(一)类与对象的概念
面向对象的概念
面向对象编程:OOP(Object-Oriented Programming)
使用类和对象开发程序的基本步骤
对于面向对象编程,主要工作就是编写类。面向对象开发的步骤:
- 开发类,类 = 属性(成员变量) + 方法
通过new关键字创建对象
示例:
- Student syx = new Student();
- 使用类中的属性和方法:对象.属性名 对象.方法名()
示例:
syx.name = "白熊";
syx.sleep();
syx.study();
运行结果
白熊在睡觉
白熊在学习
1.类与对象
(1)类是一种逻辑结构,对具有公共属性特征和行为(功能)的一个群体进行描述。例如可以定义Student类描述学生的公共属性和行为,定义一个Teacher类,描述老师的公共属性和行为。
(2)定义了类之后,就可以根据类创建(new)出一个实例。比如学生张三,老师王老师。
示例:
Teacher bai = new Teacher();
bai.name="白老师";
bai.salary = 123456;
bai.age=98;
通俗地说:
类定义了一种新的数据类型。对象就是根据类定义的变量。可以将类看做是复合类型。
类是对象的模板(template),对象是类的实例(instance)。因为对象是类的实例,所以经常会看到交换使用“对象”和“实例”这两个词。
1.1定义类
程序 = 数据 + 算法
类 = 属性 + 方法
1.2类的一般形式
class 类名 { //类名通常以大写字母开始
类型 变量1;
类型 变量2;
…
类型 方法名(参数列表) {
// 方法体
}
…
}
例如:
public class Student {//类名为”Student“
//定义属性
//成员变量/实例变量
String name;
int id;
int score;
String sex;
//成员方法
public static void sleep() {
int id;//不是成员变量
System.out.println("在睡觉");
}
}
在类中定义的变量和方法都称为类的成员。所以变量又称为成员变量,方法又称为成员方法。
1.4类的属性
类的成员变量又称为类的属性。
public class Student {
/**
*属性 成员变量
*类的{}内直接声明(定义)的变量 叫 成员变量/实例变量
*/
String name;
int age;
double score;
}
属性属于类的某个具体对象。类的每个实例(即,类的每个对象)都包含这些变量的副本,因此在类中定义的变量又被称为实例变量。
1.5类的方法
方法是对象行为特征的抽象,类具有的共性的功能操作,称之为方法。方法是个“黑匣子”,完成某个特定的应用程序功能。
方法的基本语法:
修饰符 返回类型 方法名(形参列表){
//功能代码
}
形参可以为空,可以有多个,形参的类型可以是基本类型也可以是引用类型。
public class Student {
String name;
int age;
double score;
void study(){
}
void show(){
}
}
注意:
方法中定义变量称为局部变量。
如果没有返回值,则方法的返回类型必须为void
当方法有具体的返回类型时,则必须使用return语句返回一种值。
课后总结:
Student类
public class Student {
//定义属性
//成员变量/实例变量
String name;
int id;
int score;
String sex;
//成员方法
public static void sleep() {
int id;//不是成员变量
System.out.println("在睡觉");
}
void study() {
System.out.println("在学习");
}
void kaoshi() {
System.out.println("考试不合格,抄一百遍");
}
public static void eat(String name, int num) {
System.out.println(name + "爱吃" + num + "份饭");
}
public static int shuxue() {
System.out.println("晋城人");
return 6;
}
}
Teacher类
public class Teacher {
String name;
int age;
int salary;
void hobby(){
System.out.println("老师爱吃鱼");
}
void character(){
System.out.println("该老师严格");
}
}
Other类
public class Other {
String name;
String profession;//职业
int age;
char sex;
void work(){
System.out.println("小吃店老板");
}
}
Test类
package Day02;
public class Test {
/**面向对象:万物皆对象,封装起来
*类:把共同属性的东西抽出来创建一个类(名字,年龄,性别)
*/
public static void main(String[] args) {
//学生对象--创建类
/*String name = "白熊";
int age = 20;
String sex = "女";
//String name = "白熊";//变量名name冲突*/
Student syx = new Student();
syx.name = "白熊";
syx.id = 2022005608;
syx.sex = "女";
System.out.println("--------------------------开始调用学生类----------------------------");
System.out.println("该学生名字是:"+syx.name+" 性别:"+syx.sex+" 学号:"+syx.id);
syx.sleep();
syx.shuxue();
syx.eat("大白",6);
syx.kaoshi();
Teacher bai = new Teacher();
bai.name="白老师";
bai.salary = 123456;
bai.age=98;
System.out.println("-------------------------开始调用老师类------------------------------");
System.out.println("姓名:"+bai.name+" 薪水:"+bai.salary+" 年龄:"+bai.age);
bai.character();
bai.hobby();
Other day = new Other();
day.age = 345;
day.name="白熊";
day.profession = "干饭";
day.sex = '男';
System.out.println("--------------------------开始调用其他类-----------------------------");
System.out.println("名字:"+day.name+" 年龄:"+day.age+" 职业:"+day.profession+" 性别:"+day.sex);
day.work();
}
}
运行结果
--------------------------开始调用学生类----------------------------
该学生名字是:白熊 性别:女 学号:2022005608
在睡觉
晋城人
大白爱吃6份饭
考试不合格,抄一百遍
-------------------------开始调用老师类------------------------------
姓名:白老师 薪水:123456 年龄:98
该老师严格
老师爱吃鱼
--------------------------开始调用其他类-----------------------------
名字:白熊 年龄:345 职业:干饭 性别:男
小吃店老板
(二)方法深入分析
方法可以看做是独立的功能模块,供调用模块调用,功能模块要有输入、输出,对于方法而言输入就是方法的参数,输出就是方法的返回值。调用者通过参数将需要输入的数据传递给方法,方法通过返回值将输出返回给调用者。
1.方法定义
1、方法定义包括:访问修饰符、返回类型、方法名、形参、代码块
2、方法必须有返回类型(构造方法除外),可以省略访问修饰符
3、可以有参数,也可以没有参数
2.方法调用
1、实参与形参的概念
2、方法调用的执行过程
public class Student {
//定义属性
//成员变量/实例变量
//实参
String name;
int id;
int score;
String sex;
int age;
//成员方法
public void sleep() {
//不能把形参的值反向地传送给实参。因此在函数调用过程中,
// 形参的值发生改变,而实参中的值不会变化。
//形参
int id = 123456;//不是成员变量 执行结果还是2022005608
System.out.println(name + "在睡觉");
}
}
3.参数传递
方法调用中发生的数据传送是单向的。即只能把实参的值传送给形参,而不能把形参的值反向地传送给实参。因此在函数调用过程中,形参的值发生改变,而实参中的值不会变化。
参数传递:
值传递:Swap(int a, int b)方法
引用传递(对象作为参数,本质上是引用变量作为参数)
Student类
//成员方法
public void sleep() {
//不能把形参的值反向地传送给实参。因此在函数调用过程中,
// 形参的值发生改变,而实参中的值不会变化。
int id = 123456;//不是成员变量 执行结果还是2022005608
System.out.println(name + "在睡觉");
}
Test类
syx.id = 2022005608;
System.out.println("该学生名字是:"+syx.name+" 性别:"+syx.sex+" 学号:"+syx.id);
运行结果
该学生名字是:白熊 性别:女 学号:2022005608
4.return
(1)return语句用于明确地从一个方法返回。即,return语句导致程序的执行控制转移回到方法的调用者。
(2)如果return之后还有代码也不会执行。
Student类
public int shuxue() {//返回类型为int类型
System.out.println("晋城人");
return 6;//return对应返回值类型,6-->int
//return之后还有代码也不会执行
//System.out.println("信思智学教育机构");
}
运行结果
晋城人
(3)如果方法的返回类型为void,可以使用return跳出函数,但是不能使用return返回数据。
Student类
void study() {
System.out.println(name+"在学习");
//方法的返回类型为void,可以使用return跳出函数
// 但是不能使用return返回数据。return 1;-->错误
return;//加不加return都可以
}
(4)可以返回对象。
5.方法调用
因为封装,不能直接访问其他对象的成员变量,通常是调用其他对象的方法。方法调用有两种情况:
- 调用相同类中的方法:可以直接调用。(本质上是使用this关键字调用)
Student类
String name;
int age;
public Student(String name,int age){
this.name = name;
this.age = age;//必须定义过
}
Test类
Student stu = new Student("张三",9);//有参构造
System.out.println(stu.name +stu.age+"岁");
运行结果
张三9岁
- 调用其他类中的方法:对象.方法名
Student类
void kaoshi() {
System.out.println("考试不合格,抄一百遍");
}
Test类
Student syx = new Student();
syx.kaoshi();
运行结果
考试不合格,抄一百遍
(三)构造器/构造方法
1.构造方法的语法
构造方法的作用:开辟内存空间、创建实例(创建对象)、初始化属性值。
- Student类
public Student(){//构造方法--特点--作用
}
- Test类
Student student = new Student();//开辟空间创建对象
student.name="李四"
构造方法的特点:
(1)方法名与类名相同
(2)不能声明返回类型
(3)不能使用return语句返回值
(4)通常为public
Student类
//不写,系统默认提供,写了的话不提供
//无参构造方法
//需要无参构造方法的话,则必须同时提供一个无参数的构造方法。
//无参构造方法
public Student(){//构造方法--特点--作用
}
// 有参构造方法,方法传入参数,给成员变量赋值,开辟空间创建属性
// 构造方法的特点:
//(1)方法名与类名相同 Student
//(2)不能声明返回类型 --> 无返回类型:int String void...
//(3)不能使用return语句返回值
//(4)通常为public
public Student(String name,int age){
//this关键字,假如去掉,是空,张三和9传不进去
//this用来处理属性
this.name = name;
this.age = age;//必须定义过
return;
//return 1;错误
}
Test类
Student stu = new Student("张三",9);//有参构造
System.out.println(stu.name +stu.age+"岁");
//无参构造方法:public Student(){//构造方法--特点--作用}
Student student = new Student();//假如原来的Student类中没有无参构造方法,会报错
注意:
(1)如果没有明确提供构造方法,则系统会提供一个默认的构造方法,默认构造方法(也称为缺省构造方法)没有参数。
(2)如果我们提供了一个构造方法,则系统不再提供无参数的默认构造方法。
(3)如果我们提供了一个有参数的构造方法,同时又需要无参构造方法的话,则必须同时提供一个无参数的构造方法。
课后总结
Student类
package Day02;
public class Student {
//定义属性
//成员变量/实例变量
int id;
String name;
int age;
//成员方法
public void sleep() {
//不能把形参的值反向地传送给实参。因此在函数调用过程中,
// 形参的值发生改变,而实参中的值不会变化。
int id = 123456;//不是成员变量 执行结果还是2022005608
System.out.println(name + "在睡觉");
}
void study() {
System.out.println(name+"在学习");
//方法的返回类型为void,可以使用return跳出函数
// 但是不能使用return返回数据。return 1;-->错误
//return;
}
void kaoshi() {
System.out.println("考试不合格,抄一百遍");
}
public void eat(String name, int num) {
System.out.println(name + "爱吃" + num + "份饭");
}
public int shuxue() {
System.out.println("晋城人");
return 6;
//return之后还有代码也不会执行
//System.out.println("信思智学教育机构");
}
//不写,系统默认提供,写了的话不提供
//无参构造方法
//需要无参构造方法的话,则必须同时提供一个无参数的构造方法。
public Student(){//构造方法--特点--作用
System.out.println("白熊无参");
}
//有参构造方法,方法传入参数,给成员变量赋值
//构造方法的特点:
//(1)方法名与类名相同 Student
//(2)不能声明返回类型 --> 无返回类型:int String void...
//(3)不能使用return语句返回值
//(4)通常为public
public Student(String name,int age){
this.name = name;
this.age = age;//必须定义过
}
}
Test类
package Day02;
public class Test {
/**面向对象:万物皆对象,封装起来
*类:把共同属性的东西抽出来创建一个类(名字,年龄,性别)
*/
public static void main(String[] args) {
//只要开辟新内存,并且不是有参构造,运行之后调用的就是无参构造方法
Student syx = new Student();
Student bx = syx;//引用变量赋值
syx.name = "白熊";//光有这个运行之后不输出该属性,还是输出“白熊无参”
syx.id = 2022005608;
syx.sex = "女";
Student stu = new Student("张三",9);//有参构造
System.out.println(stu.name +stu.age+"岁");
//无参构造方法:public Student(){//构造方法--特点--作用}
Student student = new Student();//假如原来的Student类中没有无参构造方法,会报错
System.out.println("--------------------------开始调用学生类----------------------------");
System.out.println(bx);//输出地址Day02.Student@776ec8df
System.out.println(syx);//输出地址Day02.Student@776ec8df
System.out.println(bx==syx);//true
System.out.println(syx==bx);//true
System.out.println("***");
bx.sleep();//引用变量赋值
syx.shuxue();
syx.eat("大白",9);
syx.kaoshi();
String str = "aaa";
String str1 = "aaa";
System.out.println(str==str1);//直接赋值相等--true
String str2 = new String("aaa");//开辟了新空间 对比的是空间是否相同 --> false
System.out.println(str==str2);
}
}
运行结果
--------------------------开始调用学生类----------------------------
该学生名字是:白熊 性别:女 学号:2022005608
白熊在睡觉
白熊在学习
Day02.Student@214c265e
Day02.Student@214c265e
true
true
***
白熊在睡觉
晋城人
大白爱吃9份饭
考试不合格,抄一百遍
true
false
(四)对象的声明与创建
Student stu1; //声明对象的引用
sut1 = new Student(); //创建对象
public static void main(String[] args) {
Student stu1 = new Student();
stu1.name = "张三"; //访问对象的属性
stu1.age = 20;
stu1.score=95.5;
stu1.show(); //方法调用
Student stu2 = new Student();
stu2.name = "李四"; //访问对象的属性
stu2.age = 22;
stu2.score=98;
stu2.show(); //方法调用
Student stu3 = stu2;
stu3.show();
}
提示:如何使用对象的成员变量和方法
注意:
属性属于类的具体对象,不同对象的属性值通常是不同的。
虽然方法也是通过对象调用的,但是各对象共享相同的方法
(五)为引用变量赋值
//Box b1 = new Box(); //创建对象,让b1指向(引用)所创建的对象
Box b2 = b1;
- Test类
Student syx = new Student();
syx.name = "白熊";
syx.id = 2022005608;
syx.sex = "女";
Student bx = syx;//引用变量赋值
System.out.println(bx);//输出地址Day02.Student@776ec8df
System.out.println(syx);//输出地址Day02.Student@776ec8df
System.out.println(bx==syx);//true
System.out.println(syx==bx);//true
System.out.println("***");
bx.sleep();//引用变量赋值
String str = "aaa";
String str1 = "aaa";
System.out.println(str==str1);//直接赋值相等--true
String str2 = new String("aaa");//开辟了新空间 对比的是空间是否相同 --> false
System.out.println(str==str2);
- 运行结果
day02.Student@214c265e
day02.Student@214c265e
true
true
***
白熊在睡觉
true
false
对象引用与对象的关系:
(1)对象引用,有时也称为对象引用变量,或称为引用变量。
(2)对象引用与对象在物理上是两个不同的东西。
(3)对于上图,我们通常说b1引用(有时也称为指向)图中的那个Box对象。
(4)对象只能通过对象引用来操作。有时直接使用对象引用代指对象,例如对于上面的例子,有时会直接将b1引用的对象称为“对象b1”或“”b1对象。
(5)将一个对象引用赋值给另一个对象引用,则两个引用变量指向同一个对象。
(6)对引用变量进行相等性比较,例如b1==b2,是比较两个引用变量是否引用同一个对象,所以b1==b2的结果为true。对对象引用进行相等性比较,有时也直接称为对象的相等性比较。
注意:基本变量与引用变量的区别
基本类型的变量位于栈内存中,引用变量所所引用的变量位于堆内存中。
(六)封装的概念
封装是面向对象的三大特征之一。
- 第一种:类中的属性自然暴露,访问修饰符,通常属性全部私有化,通过get set方法(自己提供);
- 第二种:复杂的东西变成容易操控的,封装一个方法去共同使用
面向对象的三大特征是:封装、继承、多态。
类 = 属性 + 方法,类是对属性和方法的封装。类封装了类的成员。
结合Student类,介绍后续内容:
如果在类的外部可以随意访问类的成员,那将属性和方法放到类中就没有意义了。因此Java允许在类中通过访问修饰符控制类成员的访问权限。之前已经接触过public访问修饰符。
在面向对象程式设计方法中,封装(英语:Encapsulation)是指一种将抽象性函式接口的实现细节部份包装、隐藏起来的方法。
封装可以被认为是一个保护屏障,防止该类的代码和数据被外部类定义的代码随机访问。
要访问该类的代码和数据,必须通过严格的接口控制。
封装最主要的功能在于我们能修改自己的实现代码,而不用修改那些调用我们代码的程序片段。
适当的封装可以让程式码更容易理解与维护,也加强了程式码的安全性。
1.封装的优点
- 良好的封装能够减少耦合。
- 类内部的结构可以自由修改。
- 可以对成员变量进行更精确的控制。
- 隐藏信息,实现细节。
封装是Java面向对象的三大特性之一,通常我们是通过包管理机制同时对类进行封装,隐藏其内部实现细节,通常开发中不允许直接操作类中的成员属性,所以属性一般设置为私有权限private,类中一般会给出一些公共方法来让使用者通过特定方法读取或者操作里面的数据,避免误操作从而保证了数据的安全。
2.访问控制
在完整学习访问控制之前,先熟悉一下包的概念。
2.1包与访问范围
(1)包的概念
类通常位于某个包中,包(packages)是多个类的容器。它们用于保持类的名称空间相互隔离。因此包是一种名称机制。
例如,Java类库中的类,分别包含在不同的包中:java.lang;java.util。例如String类位于java.lang包中。
(2)定义包
package pack1
(3)层次化的包
package com.xszx.sjt2111
(4)包与目录结构
位于包中的类,在文件系统中也必须有与包名层次相同的目录结构。
需要指出的是,包名与文件目录结构一致是针对.class文件而言的,对于源代码文件没有这一要求,但是通常也将源代码文件放到与包名一致的目录结构中。并且建议将源文件与类文件分开存放。
(5)导入包
类的全名为:包名.类名。例如在com.xszx包中定义的Student类,其全面为com.xszx.Student。
当在不同的包中使用某个类时,需要使用类的全名,如果包名很长的话,使用类的全名不是很方便,这时可以通过导入包来避免使用类的全名。
导入包的目的:减少输入
导入包的两种方式
import java.util.Scanner;
import java.util.*;
包既是一种命名机制,也是一种可见性控制机制。可以在包中定义该包外部的代码不能访问的类成员。
2.2访问修饰符与访问范围
类是对类成员(属性和方法)的封装,可以通过不同的访问修饰符控制类成员的可见范围,即控制类的成员在多大的范围是可见的。
- public 共有修饰符,所有类都可以访问,可以修饰类、数据成员、构造方法及方法成员
- private 私有修饰符,只有在当前类中可以调用,可以修饰数据成员、构造方法及方法成员,不可以修饰类
- protected 保护修饰符,只有子类可以调用,可以修饰数据成员、构造方法和方法成员,不可以修饰类
- 默认修饰符,只能被当前类或同包下被调用,类、方法、成员都可以使用默认权限,即不被private、protected,public修饰
类成员的访问范围划分为四个:本类、本包、子类、全局(所有包)
为类的成员指定不同的修饰符,就是控制成员的在什么样的范围内是可见的,即在什么样的范围是能够访问的。
private | 默认 | protected | public | |
同一个类中 | 是 | 是 | 是 | 是 |
相同包中的其他类 | 否 | 是 | 是 | 是 |
子类(不同包、相同包) | 否 | 否 | 是 | 是 |
全局(所有包) | 否 | 否 | 否 | 是 |
访问范围: 本类 < 本包 < 子类 < 全局(所有包)
访问修饰符:private < 缺省(default) < protected < public
- 课后总结
- User类
package day03;
public class User {
//缺省 默认default 只能在本包本类(同一个包下面的所有类)
int id;//day2Student
//公共权限,所有人所有类所有包都可以访问
public String name;
//子类访问和权限 所有子类(继承)/不同包中必须是子类(继承权限)/本类/本包
protected String sex;
//私有的,只能在本类访问 最小权限
//只能通过在本类的方法中去访问
private double money;
//这是本类中定义,不是本类中访问
//private double money1 = 3000;
/*public aaa(){
// 构造方法类型的,只能与类名相同
}*/
public void add(){
sex = "男";
//momey = 100;
id= 20223369;
name = "小草";
System.out.println("秘密");
System.out.println("性别:"+sex);
}
}
- Test2类
package day03;
public class Test2 {
public static void main(String[] args) {
User x = new User();
//User类中的id是默认类型default 权限-->同一个包下面的所有类
//Test类,Test1类
x.id = 321658;
System.out.println("default-我是本包非本类中的day03.User中的id");
//User类中的name是公共权限public 权限-->所有包所有类所有人
x.name = "public-我是本包非本类中调用的day03.User中的name";
System.out.println(x.name);
//User类中的sex是子类访问权限protected 权限-->同一个包下面的所有类
//以及不同包相同包中的子类
x.sex = "protected-我是本包本类中的day03.User中的sex";
System.out.println(x.sex);
//User类中的money是私有权限private 权限-->只能在本类中访问 最小权限
//x.money = 987;
int s = 10;
add(s);//10
}
public static void add(int i){
System.out.println(i);
}
}
- 运行结果
default-我是本包非本类中的day03.User中的id
public-我是本包非本类中调用的day03.User中的name
protected-我是本包本类中的day03.User中的sex
- day02包的Test_day3
package day02;
import day03.User;//导包
import day03.Student1;
public class Test_day3 extends User {
public static void main(String[] args) {
//包
//User类中的id是默认类型default 权限-->同一个包下面的所有类
//y.id = 2022005608;
User y = new User();
//User类中的name是公共权限public 权限-->所有包所有类所有人
y.name = "我是外包调用的name属性";
System.out.println("姓名:"+y.name);
//day3包中的User类中的sex是子类访问权限protected 权限-->同一个包下面的所有类
//以及不同包相同包中的子类
//y.sex = "女";//不同包只可以根据方法名来调用,不能够通过属性直接调用
y.add();
//User类中的money是私有权限private 权限-->只能在本类中访问 最小权限
//y.money = 654987;
}
}
- 运行结果
姓名:我是外包调用的name属性
秘密
性别:男
(七)方法重载
1. 方法重载基础
直接切入主题:
在类中可以定义名称相同的方法:只要形参列表不同即可。
特点:
1、方法名相同
2、形参列表不同:形参的类型/顺序、形参的个数
注意的地方:
1、返回值在区分重载方法时不起作用。修饰符也不起作用
2、当调用重载方法时,Java使用参数的类型和/或数量确定实际调用哪个版本。
方法重载案例:
Calculator类,添加add()方法,计算两个数的和。int float double
2.重载构造方法
Box(double length, double width, double height)
Box(double dim)
Box(Box box)
Box()
特别说明:
在某个构造方法中可以使用this()调用重载的构造方法:
public Box(double dim){
this(dim, dim, dim)
}
- 课后总结
Student1类
//方法重载
//1、方法名相同
//2、形参列表不同:形参的类型/顺序、形参的个数
//3、在某个构造方法中可以使用this()调用重载的构造方法
public void add(){
//返回值在区分重载方法时不起作用。局部变量修饰符也不起作用
//public int a;
System.out.println("add方法无参");
}
//返回值在区分重载方法时不起作用 修改修饰符也不起作用
/*private void add(){
//修饰符修改没用
System.out.println(1);
}*/
public void add(int a){
System.out.println("add方法int");
}
public void add(String a){
System.out.println("add方法String");
}
public void add(int a,String b){
System.out.println("add方法int & String");
}
Test类
Student1 c = new Student1();
c.add();
c.add(3);
c.add("aaa");
c.add(6,"kkk");
运行结果
add方法无参
add方法int
add方法String
add方法int & String
3.this关键字
在类的内部,可以在任何方法中使用this引用当前对象。
使用this关键字解决在实例变量和局部变量之间可能发生的任何名称冲突。
局部变量,包括方法的形参,可以和类的实例变量重名。当局部变量和实例变量具有相同的名称时,局部变量隐藏了实例变量。
- 课后总结
Student1类
Student1 c = new Student1();
c.add();
c.add(3);
c.add("aaa");
c.add(6,"kkk");
//不能在静态staic中调用this方法
//a.this();
//提供合理的构造方法-->一般是有参无参
public Student1(){
//this();//代表当前对象的简写new Student
//无参为了简单构造一个对象
System.out.println("我是无参构造方法");
}
public Student1(int id, String name) {
//this调用,只能是第一句
//调用另一个构造器
this(236654);
//int id= 10;
this.id = id;//把局部变量id赋值给全局变量
//使用this关键字解决在实例变量和局部变量之间可能发生的任何名称冲突
System.out.println("我是一个参数");
}
public Student1(int id) {
this();
this.id = id;
}
4.static关键字
在正常情况下,只有通过组合类的对象才能访问该类的成员。有时可能希望定义能够独立于类的所有对象进行使用的成员。为了创建这种成员,需要在成员声明的前面使用关键字static。例如Math类中的方法,就是静态方法。
方法和变量都可以声明为静态的。main()方法是最常见的静态成员的例子。main()方法被声明为静态的,因为需要在创建所有对象之前调用该方法。
5.静态变量
案例:(演示静态变量的访问方式、不同实例共享相同的值)
Math中的成员变量PI就是静态变量。
public class StaticM{
//实例变量
private int i;
//静态变量
static int si;
public static showStatic(){};
}
特别注意:
(1)被声明为静态的变量本质上是全局变量,类的所有实例共享相同的静态变量。因此,通过一个对象修改静态变量的值后,通过该类的其他对象访问到的静态变量是修改后的值。
(2)访问静态变量的方式:
类名.变量名(推荐)
对象.变量名(不推荐)
//访问静态变量的方式:
//类名.变量名(推荐)
//对象.变量名(不推荐)
Student a = new Student();
a.cd();
Student1.cd();//类名调用推荐
(3)初始化时机
静态变量:当类被虚拟机加载,静态变量就初始化,既不需要创建类的对象就可以使用静态变量。
实例变量:创建类的对象时初始化
5.1 静态代码块
静态代码块,只执行一次,而且是类加载时就执行
作用:一般完成静态变量初始化赋值或完成整个系统只执行一次的任务
5.2静态方法
最典型的静态方法是main()方法;
静态的方法有几个限制:
- 它们只能直接调用其他静态方法。
- 它们只能直接访问静态数据
- 它们不能以任何方式引用this或super关键字。(super是与继承相关的关键字,将在下一章介绍。)
5.3static的几点说明
1、static的本质作用是区分成员属于类还是属于实例。
2、通常把使用static修饰的变量和方法称为类变量和类方法,有时也称为静态变量和静态方法,把不使用static修饰的变量和方法称为实例变量和实例方法。
3、对于使用static修饰的成员,既可以通过类来调用也可以通过类的实例调用,但是建议使用类调用静态成员。对于实例变量和实例方法,则只能通过类的实例调用。
- 课后总结
Student1类
int year;
//static修饰的变量被所有实例/对象 共享
//static int year ;
//声明为静态的变量本质上是全局变量
//静态代码块
//静态代码块,只执行一次,而且是类加载时就执行
//静态代码块在一个类中可以编写多个,并且遵循自上而下的顺序依次执行。
static int cd(){
System.out.println("我是静态代码块");
return 1;
}
Test类
Student1 b = new Student1(33368,"有参构造方法",21,"女","3483539809@qq.com");
a.get();//可以直接访问静态方法,前提是修饰符可以被访问到
//不同的实例,共享相同的值,当前面定义为 int year 输出为:30 20
//当前面定义为 static int year 输出为: 20 20 后一个值覆盖前一个
a.year = 30;
b.year = 20;
System.out.println("a的年龄:"+a.year);
System.out.println("b的年龄:"+b.year);
//访问静态变量的方式:
//类名.变量名(推荐)
//对象.变量名(不推荐)
a.cd();
Student1.cd();//类名调用推荐
小知识点
public static void main(String[] args) {
int s = 10;
add(s);//输出10
}
public static void add(int i){
//并未赋值 初始值是0
System.out.println(i);
}
(八) 继承的概念
继承是面向对象的基本特征之一。
使用继承可以为一系列相关对象定义共同特征的一般类,然后其他类(更特殊的类)可以继承这个一般类,每个进行继承的类都可以添加其特有的内容。
被继承的类称为超类(super class)/父类,继承的类称为派生类/子类(subclass)。
一旦创建了一个定义一系列对象共同特征的超类,就可以使用该超类创建任意数量的更特殊的子类。
1.继承的语法
继承使用关键字extends(扩展)实现。
public class A extends SuperA{
}
子类可以从父类继承属性和部分方法,自己再增加新的属性和方法。通过继承可以重用父类的方法和属性,减少代码重复编写,便于维护、代码扩展。
Person类
public class Person {//实体类
int id;
String name;
int age = 888;
String sex;
//私有属性、私有方法不能被继承
//private double money;
//构造方法不能被继承
public Person() {
System.out.println("父类无参");
}
}
Student类
public class Student extends Person {
//子类可以增加新的属性和方法
String hobby;
//子类自己的方法
public void sleep(){
System.out.println("Student子类自己的方法"+name+"睡觉");
}
Test类
public static void main(String[] args) {
Student a = new Student();
a.id = 123;
a.name = "白熊";
a.sex = "男";
//a.age = 666;
a.eat();
//私有属性方法不能继承
//a.money = 63.2;
//构造方法不能被访问
//a.Person();
System.out.println(a);
a.sleep();
运行结果
父类无参
我是子类,我爱吃面
Person{id=123, name='白熊', age=888, sex='男'}
Student子类自己的方法白熊睡觉
2.对继承的说明
(1)子类不能从父类继承的资源:私有方法、构造方法、如果子类与父类在不同包中,子类不能继承父类中那些具有默认访问权限的方法。即不能继承那些不能访问的方法。在子类中不能访问到的那些方法,无法继承的。
理论上子类会继承父类的全部成员变量,但是子类不能访问父类的私有成员变量,如果子类与父类在不同包中,子类也不能访问父类中具有默认访问权限的成员变量。
示例
day04包的other包的Cook类
package day04.other;
import day04.teach.Person;
public class Cook extends Person {
}
Person类
//public void eat(){ //公开,这种的可以调用
void eat(){ //默认 其他包不能调用
System.out.println("继承父类今天吃鱼");
}
protected void aaa(){//继承类
System.out.println("父类aaa属性");
}
Test类
//如果子类与父类在不同包中,子类不能继承父类中那些具有默认访问权限的方法
//cook.eat();//默认方法不可访问
Cook cook = new Cook();
cook.aaa();//有权限的可以访问
(2)java类继承只允许单继承(只能有一个超类);java中接口允许多继承。
//继承只能向上继承,不能向下继承 单向的
//public class Person extends Student
//java中接口允许多继承 也就是多层继承
//public class Student extends Person,Teacher --> 错误
public class Student extends Person {
...
}
(3)子类中可以定义与父类中同名的成员变量,这时子类的成员变量会隐藏/覆盖父类中的同名成员变量。
Person类
int age = 888;
Student类
int age = 999;
Test类
Student a = new Student();
a.id = 123;
a.name = "白熊";
a.sex = "男";
a.hobby = "Student子类自己的属性撸猫";
覆盖是自己输出才能
System.out.println(a.age+"这是子类Student的年龄");
System.out.println(a.hobby);
运行结果
999这是子类Student的年龄
Person{id=123, name='白熊', age=888, sex='男'}
(4)子类中也可以定义与父类中同名的成员方法,这时子类中的方法重写了父类中的同名方法
Person类
void eat(){ //默认 其他包不能调用
System.out.println("继承父类今天吃鱼");
}
Student类
//子类中也可以定义与父类中同名的成员方法
// 这时子类中的方法重写了父类中的同名方法
void eat(){
System.out.println("我是子类,我爱吃面");
}
或者
@Override
void eat() {
//super.eat();去掉就是直接调用子类
System.out.println("我是子类,我爱吃面");
}
运行结果
我是子类,我爱吃面
3.子类的构造方法
(1)构造方法的调用顺序:在类继承层次中按照继承的顺序从父类到子类调用构造函数。
(2)在子类的构造方法中,一定会首先调用父类的构造方法。super();
Person类
//构造方法不能被继承
public Person() {
System.out.println("父类无参");
}
public Person(int id, String name, int age, String sex) {
this.id = id;
this.name = name;
this.age = age;
this.sex = sex;
System.out.println("我是父类的有参");
}
Student类
public Student() {
//子类的每个构造方法都会隐式的调用父类的无参数构造方法,
System.out.println("我是子类的无参构造");
}
public Student(int id, String name, int age, String sex){
this.id = id;
this.name = name;
this.age = age;
this.sex = sex;
System.out.println("我是子类的有参");
}
Test类
Student d = new Student();
Student s = new Student(1,"",1,"");
运行结果
父类无参
我是子类的无参构造
父类无参
我是子类的有参
(3)子类的每个构造方法都会隐式的调用父类的无参数构造方法,如果想调用父类的其他构造方法,必须使用super(参数列表)来显式调用。
(4)如果父类没有无参的构造方法,或者想调用父类的有参构造方法,则在子类的构造方法中必须显式使用super(xxx)调用父类有参构造方法。这时super(xxx)必须是子类中的第一条语句。
说明:编写类时,通常需要提供无参数构造方法。
Person类
//假如去掉无参构造方法,就会让下面的Student无参构造方法报错(不添加supre方法时)
public Person() {
System.out.println("父类无参");
}
public Person(int id, String name, int age, String sex) {
this.id = id;
this.name = name;
this.age = age;
this.sex = sex;
System.out.println("我是父类的有参");
}
Student类
@Override
void eat() {
//super.eat();去掉就是直接调用
System.out.println("我是子类,我爱吃面");
}
public Student() {
//调用构造方法必须是第一句
//在子类的构造方法中,一定会首先调用父类的构造方法。super();
//在父类没有构造方法的时候,必须显式使用super(xxx)调用父类有参构造方法,假如注释掉的话直接报错
super(1,"",1,"");
//子类的每个构造方法都会隐式的调用父类的无参数构造方法,
//如果想调用父类的其他构造方法,必须使用super(参数列表)来显式调用。
System.out.println("我是子类的无参构造");
//super(1,"",1,"");
}
public Student(String hobby) {
//先初始化父类的super(),再初始化自己的
super(1,"",1,"");
this.hobby = hobby;
eat();
System.out.println("我是子类的有参");
}
Test类
Student c = new Student("摸鱼");
Student d = new Student();
运行结果
我是父类的有参
我是子类,我爱吃面
我是子类的有参
我是父类的有参
我是子类的无参构造
(5)通常的做法:
在父类中定义有参数的构造方法,负责初始化父类的成员变量。
在子类的构造方法中,先调用父类的构造方法完成从父类继承来的那些成员变量,然后初始化子类中特有的成员变量。
注意:
如果父类中定义了一个有参数的构造方法,系统就不会再为父类提供默认的构造方法。这时,在子类的构造方法中,必须使用super(xxx)显示调用父类的有参构造方法。
4.创建多级继承层次
public GrandFather( ){
}
public Father( ) extends GrandFather{
}
public Son( ) extends Father{
}
示例:Teacher类继承Student类继承Person类
Person类
public class Person {//实体类
...
}
Student类
public class Student extends Person {
...
}
Teacher类
public class Teacher extends Student {
...
}
5.方法重写介绍
当子类从父类中继承来的方法不能满足需要时,子类可以重写该方法,重写方法要求方法名与参数列表都相同。
超类引用变量可以引用子类对象
SuperA sa; //声明超类的变量
A a = new A(); //创建子类对象
sa = a; //将子类对象赋给引用对象
sa = new A(); //创建一个新的子类对象,赋给超类引用变量
可以将子类的对象赋给父类的引用变量,但是这时使用父类的引用变量只能访问父类中定义的那些成员变量。换句话说,可以访问哪些成员是由引用变量的类型决定的,而不是由所引用的对象类型决定的。
Person类
void eat(){//默认 其他包不能调用
System.out.println("继承父类今天吃鱼");
}
Student类
//子类中也可以定义与父类中同名的成员方法
// 这时子类中的方法重写了父类中的同名方法
void eat(){
System.out.println("我是子类,我爱吃面");
}
或者
@Override
void eat() {
//super.eat();去掉就是直接调用
System.out.println("我是子类,我爱吃面");
}
Test类
Student a = new Student();
a.eat();
运行结果
我是子类,我爱吃面
6.对象的转型
Animal a = new Dog(); //小转大 可以自动进行
a.layal; //语法错误,这时通过a只能使用父类中定义的成员变量。编译时就确定
a.eat(); //
Animal a = new Dog();
d = (Dog)a; //正确 大转小, 需要强制转换
Dog d = new Animal(); //错误
Dog d = (Dog)new Animal(); //语法没错,可以编译,但运行时会抛出异常
Cat c = new Cat();
d = (Dog)c; //不正确
子类对象 赋给 父类引用 可以,自动转换
父类引用 赋给 子类引用 需要强转 ,前提:父类引用确实指向了正确的子类对象
6.1 向上转型
6.2 向下转型
(九)super关键字
关键字super用于调用/访问从父类中继承来的实例变量和方法。
super有两种一般用法。第一种用于调用超类的构造方法。第二种用于访问超类中被子类的某个成员隐藏的成员。
1.使用super()调用父类的构造方法
- 在子类中使用super()调用父类的构造方法,必须是第一条语句
- 在本类中可以使用this()调用重载的构造方法,也必须是第一条语句
- 在子类的构造方法中this()和super()不能同时使用
Person类
//构造方法不能被继承 但是子类能够使用super关键字访问
public Person() {
//System.out.println("父类无参");
}
public Person(int id, String name, int age, String sex) {
this.id = id;
this.name = name;
this.age = age;
this.sex = sex;
//System.out.println("我是父类的有参");
}
Student类
String hobby;
public Student() {
//调用构造方法必须是第一句
//在子类的构造方法中,一定会首先调用父类的构造方法。super();
//必须显式使用super(xxx)调用父类有参构造方法,假如注释掉的话直接报错
super(1,"",1,"");
//子类的每个构造方法都会隐式的调用父类的无参数构造方法,
//如果想调用父类的其他构造方法,必须使用super(参数列表)来显式调用。
//System.out.println("我是子类的无参构造");
//super(1,"",1,"");
}
public Student(String hobby) {
//在本类中可以使用this()调用重载的构造方法,也必须是第一条语句
this();
//先初始化父类的super(),再初始化自己的
//构造方法必须放前面
//在子类中使用super()调用父类的构造方法,必须是第一条语句
//super(1,"",1,"");
this.hobby = hobby;
/*eat();
System.out.println("我是子类的有参");*/
super.eat();//调用方法可以放后面
}
Test类
Person syx=new Person();
syx.id=2022005608;
syx.name = "大白";
Student a = new Student();
a.id = 123;
a.name = "白熊";
a.sex = "男";
a.hobby = "Student子类自己的属性撸猫";
System.out.println(a.hobby);
System.out.println(a);
运行结果
Student子类自己的属性撸猫
Person{id=123, name='白熊', age=1, sex='男'
2.使用super访问父类中被子类隐藏的成员变量
父类的属性被子类继承,如果子类又添加了名称相同的属性,则子类有两个相同名称的属性,如果父类型对象调用属性,就是父类的,如果是子类型对象调用就是子类的属性。
一个子类需要引用它的直接超类,都可以使用关键字super。
案例:
Person类
void add(String name,int age,String sex){
this.name = name;
this.age = age;
this.sex = sex;
System.out.println("我是父类的add方法");
}
Student类
void add(String name,int age,String sex){
this.name = name;
this.age = age;
this.sex = sex;
System.out.println("我是子类的add方法");
}
或者
@Override
//访问修饰符必须是相等或者更大
void add(String name, int age, String sex) {
//super.add(name, age, sex);
//和下面的重写方法是一样的
System.out.println("我是子类的add方法");
}
Test类
Teacher b = new Teacher();
b.id = 456;
b.sex = "男";
b.name = "白老师";
System.out.println(b);
b.add("",1,"");
运行结果
Person{id=456, name='白老师', age=1, sex='男'}
我是子类的add方法
3.Object
3.1Object类介绍
所有其他类都是Object的子类。也就是说,Object是所有其他类的超类。这意味着Object类型的引用变量可以引用任何其他类的对象。此外,因为数组也是作为类实现的,所以Object类型的变量也可以引用任何数组。
Object类定义了下面列出的方法,这意味着所有对象都可以使用这些方法。
方 法 | 用 途 |
Object clone() | 创建一个和将要复制的对象完全相同的新对象。 |
boolean equals(Object object) | 确定一个对象是否和另外一个对象相等 |
void finalize() | 在回收不再使用的对象前调用 |
Class<?> getClass() | 在运行时获取对象的类 |
int hashCode() | 返回与调用对象相关联的散列值 |
void notify() | 恢复执行在调用对象上等待的某个线程 |
void notifyAll() | 恢复执行在调用对象上等待的所有线程 |
String toString() | 返回一个描述对象的字符串 |
void wait()void wait(long milliseconds)void wait (ling milliseconds,int nanoseconds) | 等待另一个线程的执行 |
对象相等性比较
Object类中的equals()方法实现等价于“==”运算符,比较相等,如果实现对象的内容相等比较,自己的类必须重写equals方法。
例如:String类对equals()方法进行了重写,重写后的equals方法比较两个两个字符串的内容是否相同。
3.2Object类的常用方法
- lequals(Object obj)方法
比较对象相等 Object类的实现是 等价于 ==
相等的含义:两个引用是否指向同一个对象。
自己的类要比较对象相等,重写equals()方法
案例:重写Box类的equals()方法
- ltoString()方法
直接打印对象时,默认调用对象的toString()方法
Object类的toString方法输出格式:
getClass().getName() + '@' + Integer.toHexString(hashCode())
自己的类要重写toString()
案例:重写Box类的toString()方法。
- protected Object clone()
克隆对象的方法 被克隆的对象的类必须实现Cloneable接口
- lfinalize()方法 //终结方法
当垃圾回收器确定不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法。
- HashCode()方法
返回该对象的哈希码值
当我们重写equals()方法,判断两个对象相等时,最好也同时重写hascode()方法,让相同对象的哈希码值也相同
Person类
void eat(){//默认 其他包不能调用
System.out.println("继承父类今天吃鱼");
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return id == person.id &&
age == person.age &&
Objects.equals(name, person.name) &&
Objects.equals(sex, person.sex);
}
@Override
public int hashCode() {
return Objects.hash(id, name, age, sex);
}
Student类
public Student(String hobby) {
this.hobby = hobby;
super.eat();//调用方法可以放后面
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return Double.compare(student.salary, salary) == 0 &&
Objects.equals(hobby, student.hobby);
}
@Override
public int hashCode() {
return Objects.hash(hobby, salary);
}
Test类
Student q = new Student("");
q.name = "张三";
Student q1= new Student("");
q1.name ="张三";
Student w = new Student("");
w.name = "李四";
Student w1 = w;
Student w2 = w;
//比较两个对象的状态(内容)是否相同
//两个对象指向相同的引用q,那么他们一定是相等的
System.out.println(w1.equals(w2));//true
System.out.println(q1.name.equals(q));//false
System.out.println(q1.name.equals(w));//false
System.out.println(q1.name==q.name);//true
System.out.println(q.name.hashCode());//774889
System.out.println(q1.name.hashCode());//7748889
System.out.println(w.name.hashCode());//842061
运行结果
继承父类今天吃鱼
继承父类今天吃鱼
继承父类今天吃鱼
true
false
false
true
774889
774889
842061
(十)final
1.final修饰变量、方法、类、对象
- 如果final修饰变量,变量就是常量,常量不可修改,定义时必须初始化
final int i= 10;
//不能够修改
//i =2;
final String str = "白小年给";
//不能修改,会报错
//str = "cd";
- 如果final修饰方法,方法就不能被子类重写
Person类
final void eat(){ //子类不能重写
// void eat(){//默认 其他包不能调用
System.out.println("继承父类今天吃鱼");
}
Student类
//会报错
@Override
void eat(){
//super.eat();去掉就是直接调用
System.out.println("我是子类,我爱吃面"+super.age);
}
- 如果final修饰类,类就不能再被扩展,不能再有子类。Java类库中的String、Math就是final类。
public final class Person {
//修饰类,继承类不能使用
}
- final修饰对象 对象指向不能被修改 对象内容可以被修改
final Person person = new Student("白熊");
person = "白熊";
person = new Student();
2.引用类型的常量
如果常量是基本数据类型,不可以再修改。
如果常量是引用类型,不能再将其他对象赋给该引用,但可以使用该引用改变对象内部的属性。
例如
final Student s = new Student(“zhangsan”,20);
s = new Student(“李四”,20); //错误
s.setName(“李四”); //可以 正确
示例
Person类
int age = 888;
Test类
final Person person = new Person();//final修饰对象
person.age = 10;
System.out.println(person.age);
person.age = 41;
System.out.println(person.age);
Person person1 = new Person();
//不能换身 不能指向新的空间
//person = new Person();
System.out.println(person1.age);
运行结果
10
41
888
(十一)多态的概念
同一个方法名称,执行不同的操作。方法重载就是一种多态的一种形式。
1.方法重写
当子类从父类中继承来的方法不能满足需要时,子类可以重写该方法,重写方法要求方法名与参数列表都相同。因此如果子类中的方法与父类中的方法同名、并且参数类型也相同,那么子类中的方法就重写了父类中的同名方法。
为什么不直接定义另外一个方法?因为重写方法可以实现运行时多态的效果。
注意:重写只针对方法,属性没有重写概念
1.2方法重写的规则
两同:方法名相同、参数列表相同
两小:返回值类型更小(子类)或相等、抛出的异常类更小或相等
一大:访问权限更大或相等
Person类
//1 返回值类型更小(子类)或相等
public Person add (){
System.out.println("我是父类");
return new Student();
}
//2 返回值类型更小(子类)或相等
public Student add (){
System.out.println("我是父类");
return new Student();
}
//1-1
public long add (){//只能相同
System.out.println("我是父类");
return 1;
}
//2-2 访问权限更大或相等
public void add (){//父类权限大于子类
System.out.println("我是父类");
//return 1;
}
Student类
//1 返回值类型更小(子类)或相等
@Override
public Person add() {
//重写的特点
//返回类型只能相同或者更小
System.out.println("我是子类");
return new Person();
}
//2 返回值类型更小(子类)或相等
@Override
public Student add() {
//重写的特点
//返回类型只能相同或者更小
System.out.println("我是子类");
//return new Student();
return (Student) new Person();//强转
}
//1-1
public long add() {
//重写的特点
//抛出异常只能相同或者更小
System.out.println("我是子类");
//return new Student();
//return (Student) new Person();
return 1;
}
//2-2 访问权限更大或相等
void add() {//子类修饰符权限太小
//重写的特点
//抛出异常只能相同或者更小
System.out.println("我是子类");
//return new Student();
//return (Student) new Person();
//return 1;
}
Test类
public static void main(String[] args) {
Person person = new Student();
//person.add();
person.add();
}
1.3方法重写与方法重载的区别
只有当两个方法的名称和类型签名都相同时才会发生重写。如果不是都相同,那么这两个方法就只是简单的重载关系。
位置 | 方法名 | 参数表 | 返回值 | 访问修饰符 | |
方法重写 | 子类 | 相同 | 相同 | 相同或子类 | 大于等于父类 |
方法重载 | 同类 | 相同 | 不同 | 无关 | 无关 |
(十二)动态方法调度与运行时多态
1.动态方法调度
当通过父类引用调用重写方法时,在运行时会调用子类中的重写版本。
动态方法调用要以方法重写为前提。
Animal a;
a = new Dog();
a.eat(); //动态方法调度:在运行时根据超类引用指向的对象确定调用哪个方法
Person类
public class Person4 {
String name;
int age;
void keepPet(Animal animal,String something){
System.out.println("年龄为"+this.age+"的"+this.name+"会喂"+animal.color+animal.getAge()+"岁的狗狗"+something);
}
}
Animai类
public class Animal {
int age;
String color;
void eat(String something){
}
public Animal(int age, String color) {
this.age = age;
this.color = color;
}
public Animal() {
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
}
Cat类
public class Cat extends Animal{
@Override
void eat(String something) {
System.out.println(getAge()+"岁的"+getColor()+"猫吃"+something);
}
void catchMouse(){
System.out.println("小猫会抓鼠");
}
}
Dog类
public class Dog extends Animal{
@Override
void eat(String something) {
System.out.println(getAge()+"的"+getColor()+"狗吃"+something);
}
void lookHome(){
System.out.println("小狗会看家");
}
}
Test类
Animal animal= new Dog();
Animal animal1 = new Cat();
animal1.age = 3;
animal1.color = "橘红色";
animal.age = 4;
animal.color = "棕黄色";
animal.eat("骨头");
//animal.lookHome();
animal1.eat("大鱼");
//animal1.catchMouse();
Person4 person4 = new Person4();
person4.name = "白熊";
person4.age = 19;
person4.keepPet(animal1,"鲨鱼");
person4.keepPet(animal,"排骨");
运行结果
4岁的棕黄色狗吃骨头
3岁的橘红色猫吃大鱼
年龄为19的白熊会喂橘红色3岁的狗狗鲨鱼
年龄为19的白熊会喂棕黄色4岁的狗狗排骨
2.运行时多态
运行时多态的实现机理:动态方法调度
总结:方法重写是前提、动态调度是手段、多态是最终的目的
运行时多态的优点:灵活
Animal a;
a = new Dog();
a.eat();
a = new Cat();
a.eat(); //运行时多态:方法名相同,得到的结果不同
运行时多态的两个要素:
(1)在子类中重写超类中的方法。
(2)使用超类引用调用重写方法。
在自己的类中定义的toString()方法就是重写方法。
注意不要混淆:
使用超类引用调用成员变量时,调用的是超类的成员变量。
示例
Person类
public String toString() {
return "Person{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
", sex='" + sex + '\'' +
'}';
}
int age = 888;
Student类
int age = 999;
//重写toString方法
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
", sex='" + sex + '\'' +
'}';
}
Test类
Student a = new Student();
System.out.println(a.age+"这是子类Student的年龄");
System.out.println(a);
运行结果
999这是子类Student的年龄//单独输出是子类的值
Person{id=0, name='null', age=888, sex='null'}//假如没有重写子类的toString方法,输出还是调用的父类的方法
Student{id=0, name='null', age=999, sex='null'}//重写子类的toString方法
3.多态的两种形式
运行时多态:动态方法调度实现
编译时多态:重载方法,编译时通过方法匹配实现的
4.内部类和外部类
4.1 内部类和外部类概念
在Java语言中,可以把一个类定义到另外一个类的内部,在类里面的这个类就叫作内部类,外面的类叫作外部类。在这种情况下,这个内部类可以被看成外部类的一个成员(与类的属性和方法类似)。还有一种类被称为顶层(Top-level)类,指的是类定义代码不嵌套在其他类定义中的类。
示例
Test02类
package day05.teach;
public class Test02 {
private static int age = 9;
private int id;
private String name;
//外部类普通方法
public void get(){
// Test03 test03 = new Test03();
// System.out.println(test03.pwd);
System.out.println("我是外部类的普通方法get");
}
//外部类静态方法
public static void sdd(){
System.out.println("我是外部类的静态方法sdd");
}
//03内部类 可以访问外部类的所有成员
class Test03 {
//内部类不能定义静态变量
//static int b =7;
//内部类和外部类变量能够共存
private String name ="123456";
private int id = 7;
//成员内部类定义方法
public void add() {
//int id;方法在内部类里面,不需要重新定义
System.out.println("内部类" + this.id);//或者直接id;
//内部类中访问外部类的成员变量语法:外部类类名.this.变量名
System.out.println("内部类访问外部类"+Test02.this.age);
System.out.println("内部类传过来的参数是"+name);
}
}
}
Test04类
package day05.teach;
public class Test04 {
public static void main(String[] args) {
//访问内部类必须先有外部类对象
Test02.Test03 test = new Test02().new Test03();
//Test02 test = new Test02();
test.add();
}
}
运行结果
内部类7
内部类访问外部类9
内部类传过来的参数是123456
内部类可以访问外部类所有的方法和属性,如果内部类和外部类有相同的成员方法和成员属性,内部类的成员方法调用要优先于外部类即内部类的优先级比较高(只限于类内部,在主方法内,内部类对象不能访问外部类的成员方法和成员属性),外部类只能访问内部类的静态常量或者通过创建内部类来访问内部类的成员属性和方法。
(十三)抽象方法与抽象类
当编写一个类时,常常会为该类定义一些方法,这些方法用以描述该类的行为,那些这些方法都有具体的方法体。但在某些情况下,某个父类只知道其子类应该包含哪些方法,但无法准确地知道这些子类如何实现这些方法。例如Shape类的area()方法,因为Shape类的不同子类对面积的计算方法不同,即Shape类无法准确地知道其子类计算面积的方法,因此area()方法只能留给子类实现。
在某些情况下会希望定义这样一种超类,在该超类中定义了一些子类应该包含的方法,但是在超类中不能给出这些方法的有意义的实现。例如,Animal类的eat()方法、Shape类的area()方法,无法给出有实际意义的实现,对于这类方法可以声明为抽象方法。
问题:
既然父类中不能给出抽象方法的实现,为什么还要在父类中添加这些方法呢?
1. 抽象方法
抽象方法是使用abstract修饰的方法。将一个方法声明为抽象方法,从而要求子类必须重写该方法。
注意:
- 抽象方法没有方法实现,即没有方法体{},只有定义。
- 抽象类中的抽象方法的修饰符只能为public或者protected,默认为public;
- 类中如果有抽象方法,该类必须是抽象类,必须使用abstract
- 对于抽象方法,abstract不能与private、static同时使用。为父类添加抽象方法,然后让子类实现,一个主要目的就是实现多态的效果,而实现多态效果需要两个前提:一是子类重写父类中的方法,二是使用父类引用调用子类重写后的方法,根据父类实际指向的对象调用相应的重写方法。
如果将抽象方法声明为private,则子类中就无法重写该抽象方法;如果方法为static方法,则该方法属于类,而不属于某个对象,从而也就无法根据实际的指向的对象调用想用的方法。
示例
//普通类有的,都有
//无方法体{};只有定义
//public abstract void eat(){};//报错
//类中如果有抽象方法,该类必须是抽象类,必须使用abstract
public abstract void eat();
//abstruct不能与private,static同时使用
//private abstract void add();
//static abstract int a;
2.抽象类
类定义中使用abstract修饰的类为抽象类。
public abstract class Animal {
//abstract修饰类 放在类的前面
}
注意:
- 从语法上讲,抽象类中可以没有抽象方法,但是没有实际意义
- 有抽象方法的类必须是抽象类
- 不能创建抽象类的对象,即不能new对象
- 抽象类可以当做一种引用类型来使用,声明变量
- 继承自抽象类的类,必需重写抽象类中的所有抽象方法,否则自身也使用abstract修饰,即也是抽象类。
抽象类的子类,会继承抽象类中的所有抽象方法,子类要么重写所有的抽象方法。如果有一个抽象方法的没有重写的话,子类中也有抽象方法。
说明:
抽象类只定义被其所有子类共享的一般形式,而让每个子类填充其细节。这种类确定了子类必需实现的方法。
注意:
有抽象方法的一定是抽象类。
错误,因为接口中的也有抽象方法,而且接口中的所有方法都是抽象方法。
示例
Animal类
package day06.teach;
public abstract class Animal {
public abstract void eat();
//抽象方法可以有非抽象类
public void add(){
}
//不能创建对象
public Animal(){
}
//普通方法
int id;
//静态方法
public static String name;
}
Dog类
package day06.teach;
import day06.teach.Animal;
/*
继承自抽象类的类,必需重写抽象类中的所有抽象方法
否则自身也使用abstract修饰,即也是抽象类。
*/
public abstract class Dog extends Animal {
@Override
public void eat() {
}
public abstract void ssd();
}
Cat类
package day06.teach;
public class Cat extends Dog {
//继承了Dog类 重写Dog类方法
@Override
public void ssd() {
}
}
Test类
package day06.teach;
public class Test {
public static void main(String[] args) {
//不能创建抽象类的对象,即不能new对象
//Dog类也是抽象类 不能够new新对象
//Animal A= new Dog();
//Animal B= new Animal() ;
//抽象类可以当做一种引用类型来使用,声明变量
Animal A = new Cat();
//可以写 但是必须实现抽象类方法
Animal B= new Animal() {
@Override
public void eat() {
}
};
}
}
(十四)接口的概念与定义
接口可以理解为抽象到不能再抽象的类。可以认为类是一套体系,接口是另外一套体系,只不过类可以实现接口。但是不要将接口和类混为一谈。
接口中的方法全部都是抽象方法,不能存在实现的方法。
接口使用interface关键字定义,接口的定义和类很相似。
//接口的定义和类很相似
public abstract interface User {
//接口不是类 无class 也就是用interface代替class
}
1.接口中的属性和方法
(1)接口中所有方法默认是公有的抽象方法。
隐式地标识为public、abstract,并且接口中的方法也只允许使用这两个修饰符。
注意,在抽象类中必需使用abstract关键字明确指定方法为抽象方法。
//接口中只允许使用隐式地标识public abstract
//private protected ;默认是公开的,不是default
//接口中的方法默认是public、abstract,所以public abstract可以省略
public abstract void add();-->void add();
abstract void add1();-->void add1();
public void pa();-->void pa();
void a();
//接口中的方法全部都是抽象方法,不能存在实现的方法
//接口里面没有构造方法
(2)在接口中所有变量默认为公有的静态常量。
被隐式地标识为public、static、final。这意味着实现接口的类不能修改它们。同时还必须初始化它们。
int id= 50;//必须初始化
//在接口中所有变量默认为公有的静态常量 public static
//public static final可以省略 默认
public static final int i =20;-->int i =20;
注意:
- 接口能new对象吗?不可以
package day06.teach;
public interface User1 {
public void add();
//User s = new User();接口不可以new对象
//接口可以作为一种类型定义引用变量
public static final int a=4;
//接口必须是抽象方法
/*public void s(){
}*/
}
- 接口能作为一种类型定义引用变量吗? 可以 ''
2. 接口的实现
一旦定义了一个接口,一个或多个类就可以实现该接口。为了实现接口,在类定义中需要包含implements子句,然后创建接口定义的方法。
class classname implements interfacename {
//
}
示例
public abstract class UserTest implements User {
//使用关键字implementx
}
注意:
(1)实现接口的类,必须实现接口的 所有 抽象方法,如果只实现了部分抽象方法,该类必须声明为抽象类。
(2)一个类可以实现多个接口,实现的多个接口用“,”隔开
(3)实现接口的类可以同时继承一个超类,必须是 先继承后实现。
package day06.teach;
//实现接口的类可以同时继承一个超类,必须是先继承后实现。
public abstract class Userlmpl extends Animal implements User,User1 {//抽象类可以不用重写方法
//单继承 多实现接口
//实现接口必须实现里面的所有方法 假如没有全部实现,可以给类定义为抽象类
@Override
public void add() {
}
/*@Override
public void add1() {
}*/
@Override
public void add1() {
}
@Override
public void a() {
}
@Override
public void pa() {
}
//定义自己的方法
public void id(){
System.out.println("123");
}
}
package day06.teach;
//接口可以通过关键字extends继承另一个接口,其语法和类继承相同
public interface User2 extends User,User1{//接口可以实现多继承多实现
}
说明:
接口定义了一组抽象方法,实现该接口的类需要实现这些抽象方法,从而实现接口的类就具备了接口所规定的行为(功能)。
在Java中,接口可理解为对象间相互通信的协议,相当于模板。
3. 接口继承
接口可以通过关键字extends继承另一个接口,其语法和类继承相同。如果类实现的接口继承自另外一个接口,则该类必需实现在接口继承链中定义的所有方法。
4.接口的实例
实例1
定义Rectangle、Circle、Triangle类,添加适当的属性,并实现Shape接口
定义测试类测试Rectangle、Circle、Triangle类。在测试类中定义一个Shape[]数组,在该数组中存储不同的形状对象。通过循环,分别调用各个对象的area()、show()、draw()方法。
public interface Shape {
double area();
void show(); //打印输出类的成员信息
void draw(); //我是XXX(矩形、圆形、三角形)
}
Rectangle类
package day06.teach;
import jdk.swing.interop.SwingInterOpUtils;
public class Rectangle implements Shape {
String name;
int high;
int d;
public Rectangle(int high, int d) {
this.high = high;
this.d = d;
}
public Rectangle() {
}
@Override
public double area() {
System.out.println("面积为"+high*d*0.5);
return 0;
}
@Override
public void show() {
System.out.println("我是三角形");
}
@Override
public void draw() {
System.out.println(2);
}
}
Circle类
package day06.teach;
public class Circle implements Shape{
String name;
int r;
public Circle() {
}
public Circle(int r) {
this.r = r;
}
@Override
public double area() {
System.out.println("面积是"+3.14*r*r);
return 0;
}
@Override
public void show() {
System.out.println("我是圆");
}
@Override
public void draw() {
System.out.println(3);
}
}
Triangle类
package day06.teach;
public class Triangle implements Shape {
String name;
int a;
int b;
public Triangle(int a, int b) {
this.a = a;
this.b = b;
}
public Triangle() {
}
@Override
public double area() {
System.out.println("面积为"+a*b);
return 0;
}
@Override
public void show() {
System.out.println("我是矩形");
}
@Override
public void draw() {
System.out.println("1");
}
public int getA() {
return a;
}
public void setA(int a) {
this.a = a;
}
public int getB() {
return b;
}
public void setB(int b) {
this.b = b;
}
public String toString() {
return "Triangle{a = " + a + ", b = " + b + "}";
}
}
Shape接口
package day06.teach;
public interface Shape {
double area();
void show();
void draw();
}
St实现接口
package day06.teach;
public class St implements Shape {
@Override
public double area() {
return 0;
}
@Override
public void show() {
}
@Override
public void draw() {
}
}
Test01类
package day06.teach;
public abstract class Test02{
public static void main(String[] args) {
Shape shape[] = new Shape[3];
//shape[1] =new Shape(1,"aaa","三角形") ;
shape[0] = new Triangle(4,5);
shape[1] = new Rectangle(3,5);
shape[2] = new Circle(4);
for (int i = 0; i < shape.length; i++) {
System.out.println(shape[i].area());
shape[i].draw();
shape[i].show();
}
}
}
运行结果
面积为20
0.0
1
我是矩形
面积为7.5
0.0
2
我是三角形
面积是50.24
0.0
3
我是圆
5.抽象类和接口的区别
补充:
抽象类有构造方法,接口没有构造方法
类只能单继承,接口可以多重继承接口
抽象类中可以没有抽象方法,但是有抽象方法的类必须是抽象类。
抽象类:在Java中被abstract关键字修饰的类称为抽象类,被abstract关键字修饰的方法称为抽象方法,抽象方法只有方法的声明,没有方法体。抽象类的特点:
a、抽象类不能被实例化只能被继承;
b、包含抽象方法的一定是抽象类,但是抽象类不一定含有抽象方法;
c、抽象类中的抽象方法的修饰符只能为public或者protected,默认为public;
d、一个子类继承一个抽象类,则子类必须实现父类抽象方法,否则子类也必须定义为抽象类;
e、抽象类可以包含属性、方法、构造方法,但是构造方法不能用于实例化,主要用途是被子类调用。
接口:Java中接口使用interface关键字修饰,特点为:
a、接口可以包含变量、方法;变量被隐士指定为public static final,方法被隐士指定为public abstract(JDK1.8之前);
b、接口支持多继承,即一个接口可以extends多个接口,间接的解决了Java中类的单继承问题;
c、一个类可以实现多个接口;
面试题:接口与抽象类的区别
相同点
(1)都不能被实例化
(2)接口的实现类或抽象类的子类都只有实现了接口或抽象类中的方法后才能实例化。
不同点
(1)接口只有定义,不能有方法的实现,java 1.8中可以定义default方法体,而抽象类可以有定义与实现,方法可在抽象类中实现。
(2)实现接口的关键字为implements,继承抽象类的关键字为extends。一个类可以实现多个接口,但一个类只能继承一个抽象类。所 以,使用接口可以间接地实现多重继承。
(3)接口强调特定功能的实现,而抽象类强调所属关系。
(4)接口成员变量默认为public static final,必须赋初值,不能被修改;其所有的成员方法都是public、abstract的。抽象类中成员变量 默认default,可在子类中被重新定义,也可被重新赋值;抽象方法被abstract修饰,不能被private、static、synchronized和 native等修饰,必须以分号结尾,不带花括号。
(十五)包装类
一方面出于性能方面的考虑,java为数值使用基本类型,而不是对象。基本类型不是对象层次的组成部分,它们不继承Object。
另一方面有时需要创建表示基本类型的对象,例如集合类只处理对象。为了在类中存储基本类型,需要将基本类型包装到一个类中,为此Java为8种基本数据类型分别提供了对应的包装类。本质上这些类将基本类型包装到一个类中,因此通常将它们称为类型包装器。包装器类位于Java.lang包中。
1.八个包装类
基本数据类型 | 引用数据类型 |
byte | Byte |
short | Short |
int | Integer |
long | Long |
char | Float |
float | Double |
double | Character |
boolean | Boolean |
2. Character包装器
Character是char类型的包装器。Character的构造函数为:
Character(char ch)
其中,ch指定了将由即将创建的Character对象包装的字符。
为了获取Character对象中的char数值,可以调用charValue(),如下所示:
char charValue( )
该方法返回封装的字符。
3. Boolean包装器
Boolean是包装boolean值的包装器。它定义了以下构造函数:
Boolean(boolean boolValue)
Boolean(String boolString)
在第一个版本中,boolValue必须是true或false。在第二个版本中,如果boolString包含字符串“true”(大写或小写形式都可以),则新的Boolean对象将为真,否则,将为假。
为了从Boolean对象获取boolean值,可以使用booleanValue(),如下所示:
boolean booleanValue( )
该方法返回与调用对象等价的boolean型值。
4. 数值类型的包装器类
1、构造器
所有数值类型包装器都定义了用于从给定数值或数值的字符串表示形式构造对象的构造函数,例如,下面是为Integer定义的构造器:
Integer(int num)
Integer(String str)//a123 “123”
如果str没有包含有效的数字值,则会抛出NumberFormatException异常。
2、从包装器对象中提取数值
最常用类型的包装器是那些表示数值的包装器。包括Byte、Short、Integer、Long、Float以及Double。所有这些数值类型包装器都继承自抽象类Number。Number声明了以不同数字格式从对象返回数值的方法,如下所示:
byte byteValue( )
double doubleValue( )
float floatValue( )
int intValue( )
long longValue( )
short shortValue( )
3、将包装器对象转换成字符串
类型包装器都重写了toString()方法,该方法可以将数值转换成字符串形式。
String str = Integer.toString(100);
案例:包装器类测试
5.自动装箱与自动拆箱
自动装箱是这样一个过程,只要需要基本类型的对象,就自动将基本类型自动封装(装箱)进与之等价的类型包装器中,而不需要明确地构造对象。自动拆箱是当需要时自动抽取(拆箱)已装箱对象数值的过程。不需要调用intValue()或doubleValue()这类方法。
自动装箱和自动拆箱特性极大地简化了一些算法的编码,移除了单调乏味的手动装箱和拆箱数值操作。它们还有助于防止错误。此外,它们对于泛型非常重要,因为泛型只能操作对象。最后,集合框架需要利用自动装箱特性进行工作。
案例:自动装箱与自动拆箱测试
6.数值与字符串形式之间的转换
最常见的编程杂务之一是将数值的字符串表示形式转换成数值。数值类型的包装器类为此提供了相应的方法。例如:
- Int类的parseInt()方法
- Long类的parseLong()方法
- Double类的parseDouble()方法
为了将数值转换成字符串形式,可以调用相应包装类的toString()方法。
说明:
各包装器类以静态方法的形式提供了许多很有用的辅助功能,请查阅帮助文档。
String.valueOf()
Int.toString();
Double.toString();
int I = 100;
String str = I + “”;
测试案例:
package day06.teach;
public class Test04 {
public static void main(String[] args) {
//变量转化为对象
//基本类型和对象相互转换
char a = 'o';
Character ch1 = new Character(a);
System.out.println(ch1);
System.out.println(a);
Character ch = new Character('p');
//获取Character对象中的char数值
char c = ch.charValue();
System.out.println(ch);
System.out.println(c);
int i = 10;
int j = 20;
System.out.println(""+i+j);
//装箱
Integer integer = new Integer(123);
//把对象提取出来
String s = integer.toString();
System.out.println(s);
//不需要new出来新对象
Integer k =10;//自动分装到类里面 k是对象
System.out.println(i==k);//比较的时候自动拆箱,把对象变成基本数据类型
Integer C = new Integer(10);
System.out.println(C.equals(i));
/*String l = "白熊123";
int b =Integer.parseInt(l);
System.out.println(b);*/
//byte直接比较字节,其余的是对象比较 128以内 大于的话,int
int m =1000;
Integer m1 = 1000;
Integer m2 = 1000;
Integer m3 = 100;
Integer m4 = 100;
System.out.println(m==m1);//true
System.out.println(m2==m1);//false
System.out.println(m3==m4);//true
}
}
运行结果
o
o
p
p
1020
123
true
true
true
false
true
11.7.3 字符分类
Character类提供一些静态方法用于判断字符属于哪一类。
static boolean isDigit(char ch) | 如果ch是数字,则返回true。 |
static boolean isLetter(char ch) | 如果ch为字母,则返回true。 |
static boolean isLetterOrDigit(char eh) | 如果ch为字母或数字,则返回true。 |
static boolean isLowerCase(char ch) | 如果ch为小写字母,则返回true; |
static boolean isUpperCase(char ch) | 如果ch为大写字母,则返回true。 |
static boolean isSpaceChar(char ch) | 如果ch为空格字符,则返回true。 |
static boolean isWhitespace(char ch) | 如果ch为空白字符,则返回true。 |
示例
package day07.teach;
public class Test01 {
public static void main(String[] args) {
//创建字符类对象
Character character = new Character('a');
System.out.println(character);
//如果ch是数字,则返回true。
System.out.println(Character.isDigit('1'));//T
//如果ch为字母,则返回true。
System.out.println(Character.isLetter('A'));//T
//如果ch为字母或数字,则返回true。
System.out.println(Character.isLetterOrDigit('a'));//T
//如果ch为小写字母,则返回true;
System.out.println(Character.isLowerCase('A'));//F
//如果ch为大写字母,则返回true。
System.out.println(Character.isUpperCase('a'));//F
//如果ch为空格字符,则返回true。 空格只包括空格
System.out.println(Character.isSpaceChar(' '));//T
//如果ch为空白字符,则返回true。
//空白符包含:空格、tab键、换行符。\n \t 再加
System.out.println(Character.isWhitespace('\n'));//T
}
}
11.7.4 包装器类中其他常用的常量和方法
Integer.MAX_VALUE//***\*表示int数据类型的最大取值数:2 147 483 647\****
Integer.MIN_VALUE//***\*表示int数据类型的最小取值数:-2 147 483 648\****
Integer.SIZE //长度,多少bit
Integer.valueOf(100); //根据整数创建Integer对象
Integer. valueOf("100"); //根据字符串创建Integer对象
示例
package day07.teach;
public class Test02 {
public static void main(String[] args) {
//表示int数据类型的最大取值数:2 147 483 647
System.out.println(Integer.MAX_VALUE);//2147483647
//表示int数据类型的最小取值数:-2 147 483 648
System.out.println(Integer.MIN_VALUE);//-2147483648
//长度,多少bit
System.out.println(Integer.SIZE);//32
//根据整数创建Integer对象
System.out.println(Integer.valueOf(100));//100
//根据字符串创建Integer对象
System.out.println(Integer. valueOf("100"));//100
}
}
(十六) String
11.2.1 String类介绍
(1)创建的每个字符串实际上都是String类的对象。即使是字符串字面值实际上也是String对象。
(2)String类型的对象是不可变的;一旦创建了一个String对象,其内容就不能再改变。即,一旦创建了一个String对象,就不能改变该字符串包含的字符。
所谓Stirng类型对象中的字符串是不可改变的,是指创建了String实例后不能修改String实例的内容。但是可以修改String引用变量,使其指向其他String对象。
示例
package day07.teach;
public class Test {
public static void main(String[] args) {
//不能修改String实例内容(String类型的对象是不可变的),但是可以修改String引用变量
//使其指向其他String对象
//修改指向内存地址
String str = "qqq";
str = "www";
String st = "q";
System.out.println(str);
}
}
当每次需要已存在字符串的修改版本时,会创建包含修改后内容的新String对象。原始字符串仍然没有改变。使用这种方法的原因是,实现固定的、不能修改的字符串与实现能够修改的字符串相比效率更高。
(3)对于那些需要能够修改的字符串的情况,Java提供了两个选择:StringBuffer和StringBuilder。这两个类都包含在创建之后可以进行修改的字符串。
(4)String、StringBuffer和StringBuilder类都是在java.lang包中定义的。这三个类都实现了CharSequence接口。
注意:API文档的使用
11.2.2 String类的构造方法
String(); //创建不包含内容的字符串对象
String(char[ ] chars)
String(char[ ] chars, int startIndex, int numChars)
String(String strObj)
还可以直接使用字符串字面创建String对象:String str = “abc”;
注意:没有使用单位字符作为参数的构造器。
//String(char c);
示例
package day07.teach;
public class Test03 {
public static void main(String[] args) {
创建不包含内容的字符串对象
String s = new String();
//分配一个新的 String,它包含取自字符数组参数一个子数组的字符。
// offset 参数是子数组第一个字符的索引count 参数指定子数组的长度。
// 该子数组的内容已被复制;后续对字符数组的修改不会影响新创建的字符串。
char[] a = new char[5];
String s1 = new String(a,0,4);
//分配一个新的 String,它包含 Unicode 代码点数组参数一个子数组的字符。
// offset 参数是该子数组第一个代码点的索引,count 参数指定子数组的长度。
// 将该子数组的内容转换为 char;后续对int 数组的修改不会影响新创建的字符串。
int[] b = new int[5];//byte也可以
String s4 = new String(b,0,4);
//分配一个新的 String,使其表示字符数组参数中当前包含的字符序列。
//该字符数组的内容已被复制;后续对字符数组的修改不会影响新创建的字符串
String s2 = new String(a);//b不行
String c = "白熊";
String s3 = new String(c);
//没有使用单位字符作为参数的构造器
//char d = 'q';
//String s5 = new String(d);
//能够使用String对象的任何地方都可以使用字符串字面值
System.out.println("qwerfg".length());
}
}
说明:
因为会为字符串字面值创建String对象,所以在能够使用String对象的任何地方都可以使用字符串字面值。
System.out.println("abc".length());
字符串比较
11.2.3.1 字符串相等性比较
String类重写了equals()方法,重写后的方法比较两个字符串对象的内容是否相同。
运算符“==”比较两个String引用是否指向同一个String对象。
11.2.3.2 其他比较方法
1.将此String与另一个String比较,不考虑大小写。
将此 String 与另一个 String 比较,不考虑大小写。如果两个字符串的长度相同,并且其中的相应字符都相等(忽略大小写),则认为这两个字符串是相等的。
boolean equalsIgnoreCase(String str)
2.测试此字符串是否以指定的后缀结束。
boolean endsWith(String suffix)
3.测试此字符串是否以指定的前缀开始。
boolean startsWith(String prefix)
4.按照字典顺序比较两个字符串。
int compareTo(String str)
其中,str是将要与调用String对象进行比较的String对象。返回的比较结果及其解释如下所示:
值 | 含 义 |
小于0 | 调用字符串小于str。 |
大于0 | 调用字符串大于str。 |
0 | 两个字符串相等。 |
5.按字典顺序比较两个字符串,不考虑大小写。
int compareToIgnoreCase(String str)
示例
package day07.teach;
public class Test04 {
public static void main(String[] args) {
String str = "qqq";
String str1 = "qqQ";
String str2 = "weqqq";
String qz = "we";
String s =new String();
//方法()
//字符串长度
System.out.println(str.length());
//将此String与另一个String比较 考虑大小写
System.out.println(str.equals(str2));//T
//将此 String 与另一个 String 比较,不考虑大小写。
//两个字符串的长度相同相应字符都相等(忽略大小写)
System.out.println(str.equalsIgnoreCase(str1));//T
//测试此字符串是否以指定的后缀字符串结束
System.out.println(str.endsWith("q"));//T
//测试此字符串是否以指定的前缀开始。
System.out.println(str2.contains(qz));//T
//按照字典顺序比较两个字符串。返回值为int
System.out.println(str.compareTo(str1));//32
//按字典顺序比较两个字符串,不考虑大小写
System.out.println(str.compareToIgnoreCase(str1));//0
}
}
11.2.4 字符串连接
String str1 = “abc” ;
String str2 = “def”;
String str3 = “hij”;
String str1 = str1 + str2;
String类型的引用,指向的字符串对象是不能修改的。
String表示不可变的字符串,只要创建了字符串对象,那么这个对象的内容就不能再改变。
API里面是否有拼接方法?
查看了 真没有
11.2.5 字符串查找
1.当且仅当此字符串包含指定的字符串序列时,返回true。
CharSequence 表示字符串序列,是String的父类
boolean contains(CharSequence s)
2.返回指定字符/子串第一次出现处的索引。
int indexOf(int ch)和int indexOf(String str)
3.返回指定字符/子串最后一次出现处的索引。
int lastIndexOf(int ch)和int lastIndexOf(String str)
示例
package day07.teach;
public class Test05 {
public static void main(String[] args) {
String str = "abcd";
char s = 'a';
String str2 = "bbbb";
String str3 = "12ej4k";
//当且仅当此字符串包含指定的字符串值序列时,返回true。
System.out.println(str.contains("a"));//T
//返回指定字符/子串第一次出现处的索引
System.out.println(str.indexOf("d"));//3
System.out.println(str3.indexOf("4"));//4
//返回指定字符/子串最后一次出现处的索引
System.out.println(str2.lastIndexOf("b"));//3
}
}
说明:
(1)当没有字符/子串没有出现时,返回值为-1。可以使用该方法判断字符/子串是否存在。
(2)可以使用下面这些重载形式指定查找的开始位置:
int indexOf(int ch, int startIndex)
int lastIndexOf(int ch, int startIndex)
int indexOf(String str, int startIndex)
int lastIndexOf(String str, int startIndex)
11.2.6 字符串修改
1.将指定字符串连接到此字符串的结尾,concat()与“+”执行相同的功能。
String concat(String str)
2.返回一个新的字符串,它是通过用newChar替换此字符串中出现的所有oldChar得到的。
String replace(char oldChar, char newChar)
3.使用默认语言环境的规则将此String中的所有字符都转换为小写。
String toLowerCase()
4.使用默认语言环境的规则将此String中的所有字符都转换为大写。
String toUpperCase()
5.返回字符串的副本,删除前导空白和尾部空白。
String trim( )
示例
package day07.teach;
import jdk.swing.interop.SwingInterOpUtils;
public class Test06 {
public static void main(String[] args) {
String str = "asdf";
String str1 = "zxcv";
String str2 = "QWER";
String str3 = " ert345 lkj ";
//将指定字符串连接到此字符串的结尾,concat()与“+”执行相同的功能。
System.out.println(str.concat(str1));//asdfzxcv
//返回一个新的字符串,它是通过用newChar替换此字符串中出现的所有oldChar得到的。
System.out.println(str.replace("as","11"));//11df
//使用默认语言环境的规则将此String中的所有字符都转换为小写。
System.out.println(str2.toLowerCase());//qwer
//使用默认语言环境的规则将此String中的所有字符都转换为大写。
System.out.println(str.toUpperCase());//ASDF
//返回字符串的副本,删除前导空白和尾部空白 不包括中间
System.out.println(str3); // ert345 lkj
System.out.println(str3.trim());//ert345 lkj
}
}
提取字符与子串
1.返回指定索引处的char值。
str.charAt();
2.将此字符串转换为一个新的字符数组。
char[ ] toCharArray()
3.返回一个新的字符串,它是此字符串的一个子字符串。该子字符串始于指定索引处的字符,一直到此字符串末尾。
String substring(int beginIndex)
4.返回一个新字符串,它是此字符串的一个子字符串。该子字符串从指定的beginIndex处开始,一直到索引endIndex-1处的字符。
String substring(int beginIndex, int endIndex)
示例
package day07.teach;
public class Test07 {
public static void main(String[] args) {
String str = "abchji876";
//返回指定索引处的char值。
System.out.println(str.charAt(0));//a
//将此字符串转换为一个新的字符数组。
System.out.println(str.toCharArray());//abchji876
//返回一个新的字符串,它是此字符串的一个子字符串。
//该子字符串始于指定索引处的字符,一直到此字符串末尾。
System.out.println(str.substring(1));//bchji876
//返回一个新字符串,它是此字符串的一个子字符串。
// 该子字符串从指定的beginIndex处开始,一直到索引endIndex-1处的字符。
System.out.println(str.substring(1,5));//bchj
}
}
11.2.8 其他字符串常用方法
返回此字符串的长度
注意:数组的长度为数组对象.length 属性
int length()
根据给定正则表达式的匹配拆分此字符串。最简单的使用方法是为参数regex
String[] split(String regex)
将数据从内部格式转换成人类可读的形式。它是一个静态方法。
对于大部分数组,valueOf()会返回一个相当隐蔽的字符串,表明这是某种类型的数组。然而,对于字符数组,会创建包含字符数组中字符的String对象。
static String valueOf( int i)
示例
package day07.teach;
public class Test08 {
public static void main(String[] args) {
String str = " asd,fagh,56,a7 gh,ajk";
//返回数组长度 带空格
System.out.println(str.length());//22
//以传入的分隔符参数拆分该字符串 返回一个字符串数组String[]
String[] str2 = str.split(",");
for (int i = 0; i < str2.length; i++) {
System.out.println("第"+(i+1)+"个"+str2[i]);
}
//split(regex)方法最终返回一个字符串数组 故结果是字符串数组的地址
System.out.println(str2);//[Ljava.lang.String;@53d8d10a
//将 int 变量 i 转换成字符串
int i = 456789;
String str1 = String.valueOf(i);
System.out.println(str1);
//判断数据类型是否是String
System.out.println(str1 instanceof String);//true
}
}
运行结果
22
第1个 asd
第2个fagh
第3个56
第4个a7 gh
第5个ajk
[Ljava.lang.String;@53d8d10a
456789
true
(十七) StringBuffer和StringBuilder
1.StringBuffer与StringBuilder类介绍
StringBuffer是String的对等类,提供了许多字符串功能。您可能知道,String表示长度固定、不可修改的字符序列。与之相对应,StringBuffer表示可增长、可写入的字符序列。StringBuffer允许在中间插入字符和子串,或在末尾追加字符和子串。StringBuffer能够自动增长,从而为这类添加操作准备空间,并且通常预先分配比实际所需更多的字符空间,以允许空间增长。
StringBuilder类是由JDK5引入的,以增加Java的字符串处理能力,提供与StringBuffer相同的功能。
StringBuffer与StringBuilder的区别:
- StringBuffer类是线程安全的,而StringBuilder则不是,即不保证其对象的同步性,在多线程环境中是不安全的。
- StringBuilder在性能上要比StirngBuffer好一些。
2.StringBuffer类的构造方法
StringBuffer( ) //默认预留16个字符的空间
StringBuffer(int size) //size指定预留的字符空间
StringBuffer(String str) //额外预留16个字符的空间 abc+16
StringBuffer(CharSequence chars) //额外预留16个字符的空间
提示:
再次分配内存空间是很耗时的操作。此外,频繁分配空间会产生内存碎片。
示例
package day07.teach;
public class Test09 {
public static void main(String[] args) {
String str = "a1234";
System.out.println(str.length());//5
StringBuffer stringBuffer1 = new StringBuffer(str);
System.out.println(str.length());//5
StringBuffer stringBuffer = new StringBuffer("qwe123456123456");
System.out.println(stringBuffer);//qwe123456123456
}
}
3.StringBuffer类的常用方法
1.append ()-->拼接
append()方法将各种其他类型数据的字符串表示形式连接到调用StringBuffer对象的末尾。该方法有多个重载版本,下面是其中的几个:
StringBuffer append(String str)
StringBuffer append(int num)
StringBuffer append(Object obj)
示例
StringBuffer stringBuffer = new StringBuffer("ABC");
StringBuffer stringBuffer1 = new StringBuffer("123");
//StringBuffer append(Object obj)
System.out.println(stringBuffer.append(stringBuffer1));//ABC123
//StringBuffer append(String str)
System.out.println(stringBuffer.append("qqq"));//ABC123qqq
//StringBuffer append(int num)
System.out.println(stringBuffer.append(1));//ABC123qqq1
2.insert ()-->插入
在指定位置插入参数提供的内容,返回修改后的该StringBuffer对象引用。该方法有多个重载版本,下面是其中的几个:
StringBuffer insert(int index, String str)
StringBuffer insert(int index, char ch)
StringBuffer insert(int index, Object obj)
示例
String str3 = "百度";
StringBuffer stringBuffer = new StringBuffer("asdfgh789");
StringBuffer stringBuffer1 = new StringBuffer("美团");
//StringBuffer insert(int index, String str)
System.out.println(stringBuffer.insert(1,"白熊"));//a白熊sdfgh789
//StringBuffer insert(int index, char ch)
System.out.println(stringBuffer.insert(1,'A'));//aA白熊sdfgh789
//StringBuffer insert(int index, Object obj)
System.out.println(stringBuffer.insert(1,str3));//a百度A白熊sdfgh789
System.out.println(stringBuffer.insert(1,stringBuffer1));//a美团百度A白熊sdfgh789
3.StringBuffer delete (int start,int end) 10 包含start 不包含end
删除从start开始到end-1为止的一段字符序列,返回修改后的该StringBuffer对象引用。
示例
StringBuffer stringBuffer6 = new StringBuffer("0123456789");
System.out.println(stringBuffer6.delete(0,6));//6789
4.StringBuffer deleteCharAt(int index)
移除指定位置的字符,返回修改后的该StringBuffer对象引用。
示例
StringBuffer stringBuffer6 = new StringBuffer("0123456789");
System.out.println(stringBuffer6.delete(0,6));//6789
//移除指定位置的字符,返回修改后的该StringBuffer对象引用。
//此时的stringBuffer6已经变成了6789 --> 删除第一个元素
System.out.println(stringBuffer6.deleteCharAt(0));//789
5.StringBuffer reverse()
将字符序列逆序,返回修改后的该StringBuffer对象引用。
//将字符序列逆序,返回修改后的该StringBuffer对象引用。
System.out.println(stringBuffer6.reverse());//987
6.StringBuffer setCharAt( (int index,char ch) 5 a
将指定索引处的字符设置为 ch,返回修改后的该StringBuffer对象引用。
示例
//将指定索引处的字符设置为 ch,返回修改后的该StringBuffer对象引用
StringBuffer stringBuffer7 = new StringBuffer("白熊是只狗熊");
stringBuffer7.setCharAt(1,'页');
System.out.println(stringBuffer7);//白页是只狗熊
public static void main(String[] args){
StringBuffer sb = new StringBuffer();
sb += “I”;//赋值类型不匹配
sb.append(“am”);
sb.append(true);
System.out.println(sb);
}
几点说明
(1)StringBuffer给对象赋值的方法以及与String类的相互转换
(2)StringBuffer对象不能使用 += 赋值
(3)注意使用StringBuffer的append()方法连接字符串与使用“+”运算符直接连接String对象的区别。
StringBuffer stringBuffer8 = new StringBuffer();
//stringBuffer8 += " ";
//StringBuffer和 String属于不同的类型,不能直接进行强制类型转换
//stringBuffer8 = "我是白熊";
//stringBuffer8 = (StringBuffer)"456";
//转换如下
String name = "白熊";
//String 转换为StringBuffer
StringBuffer stringBuffer9 = new StringBuffer(name);
//StringBuffer转换为String
String name1 = stringBuffer9.toString();
//StringBuffer的赋值方法
//1-构造方法
//StringBuffer stringBuffer7 = new StringBuffer("白熊是只狗熊");
//2-StringBuffer中的append方法
//StringBuffer stringBuffer9 = new StringBuffer();
//stringBuffer9.append("百而");
4. 长度与容量的概念
长度是指StringBuffer中实际保存的字符的个数,容量是指已经分配的空间大小。
获取StringBuffer对象的当前长度
int length()
设置StringBuffer对象中字符串的长度。当增加字符串的大小时,会向末尾添加空字符。如果调用setLength()时使用的值小于length()返回的当前值,则超出新长度的字符将丢失。
void setLength(int len) 50 20 6 50 10
获取StringBuffer对象的当前容量
int capacity( )
设置缓存的大小。minCapacity指定了缓存的最小尺寸。(出于效率考虑,可能会分配比minCapacity更大的缓存。)
void ensureCapacity(int minCapacity)
扩容后的数组长度为字节数组定长*2 +2 ,扩容不够时直接扩容到最大(指定大小)。
示例
StringBuffer stringBuffer10 = new StringBuffer("白熊有小狐狸,不需要大饼");
//实际保存的字符的个数
System.out.println(stringBuffer10.length());//12
//已经分配的空间大小 获取StringBuffer对象的当前容量
System.out.println(stringBuffer10.capacity());//12+16=28
(十八)Math类
1.Math函数中常用的一些方法
类Math包含用于执行基本数字运算的方法
1.1算术运算
方法名 | 作用 |
Math.abs(a) | 取a的绝对值 |
Math.sqrt(a) | 取a的平方根 |
Math.cbrt(a) | 取a的立方根 |
Math.max(a,b) | 取a、b之间的最大值 |
Math.min(a,b) | 取a、b之间的最小值 |
Math.pow(a,b) | 取a的b平方 |
示例
//Math.abs(a):取a的绝对值
System.out.println(Math.abs(-45));
//Math.sqrt(a):取a的平方根 开根号
System.out.println(Math.sqrt(3));
//Math.cbrt(a):取a的立方根 开根号
System.out.println(Math.cbrt(2));
//Math.max(a,b):取a、b之间的最大值
System.out.println(Math.max(99,98));
//Math.min(a,b):取a、b之间的最小值
System.out.println(Math.min(99,98));
//Math.pow(a,b):取a的b平方
System.out.println(Math.pow(2,4));
1.2算术进位
方法名 | 作用 |
Math.ceil() | 逢余进一(返回大于或等于参数且等于整数的最小值) |
Math.floor() | 逢余舍一(取小于等于a的最大整数) |
Math.rint() | 四舍五入 |
Math.round() | 四舍五入 |
示例
//Math.ceil():逢余进一
System.out.println(Math.ceil(9.8));
//Math.floor():逢余舍一
System.out.println(Math.floor(9.8));
//Math.rint():四舍五入 返回double值 在0.5时取偶数0.0
System.out.println(Math.rint(5.4));
//Math.round():四舍五入 double时返回long值,float时返回int值
System.out.println(Math.round(6.8))
1.3随机数
Math.random() 随机数,在范围 [0.0,1.0) 内随机取一个值
示例
//random() 返回带有正号的 double值,大于或等于 0.0且小于 1.0 。
System.out.println(Math.random());//0-1
System.out.println(Math.random()+1);//1-2
System.out.println(Math.random()*10);//0-10
System.out.println(Math.random()*10+1);//1-11
System.out.println(Math.random()*100+0.5);//0.5-100.5
Random random = new Random();
int s = random.nextInt();
1.4三角函数
方法名 | 作用 |
Math.sin() | 正弦 |
Math.cos() | 余弦 |
Math.ten() | 正切 |
sin() | 正弦 |
示例
//Math.sin():正弦
System.out.println(Math.sin(30));
//Math.cos():余弦
System.out.println(Math.cos(30));
//Math.ten():正切
System.out.println(Math.tan(30));
总结在Math函数中,还有许多关于数字的基本运算,但是基本上常用的一些方法都在上文有详细的列举,对于这些常用的方法还是需要熟练运用,避免在开发过程中或平时做任务遇到时,不知所措。
1.5静态常量
Math类中包含E和PI两个静态常量,正如它们名字所暗示的,它们的值分别等于e(自然对数)和 π(圆周率)。
System.out.println("E常量的值:"+Math.E);
System.out.println("PI常量的值:"+Math.E);
运行结果如下:
E常量的值:2.718281828459045
PI常量的值:2.718281828459045
1.51最值和绝对值
在程序中常见的就是求最大值,最小值和绝对值问题,如果使用Math类提供的方法可以很容易实现,这些方法如表所示:
方法 说明
static int abs(int a) 返回a的绝对值
static long abs(long a) 返回a的绝对值
static float abs(float a) 返回a的绝对值
static double abs(double a) 返回a的绝对值
static int max(int x,int y) 返回x和y中的最大值
static double max(double x,double y) 返回x和y中的最大值
static long max(long x,long y) 返回x和y中的最大值
static float max(float x,float y) 返回x和y中的最大值
static int min(int x,int y) 返回x和y中的最小值
static long min(long x,long y) 返回x和y中的最小值
static double min(double x,double y) 返回x和y中的最小值
static float min(float x,float y) 返回x和y中的最小值
1.52求整运算
Math类的求整方法如表所示:
方法 说明
static double ceil(double a) 返回大于或等于a的最小整数
static double floor(double a) 返回小于或等于a的最大整数
static double rint(double a) 返回最接近a的整数值,如果有两个同样接近的整数,则结果取偶数
static int round(float a) 将参数加上1/2后返回与参数最近的整数
static long round(double a) 将参数加上1/2后返回与参数最近的整数,然后强制转换
1.53三角函数运算
Math类中包含的三角函数方法及其说明如表所示:
方法 说明
static double sin(double a) 返回角的三角正弦值,参数以孤度为单位
static double cos(double a) 返回角的三角余弦值,参数以孤度为单位
static double asin(double a) 返回一个值的反正弦值,参数域在[-1,1],值域在[-PI/2,PI/2]
static double acos(double a) 返回一个值的反余弦值,参数域在[-1,1],值域在 [0.0,PI]
static double tan(double a) 返回角的三角正切值,参数以弧度为单位
static double atan(double a) 返回一个值的反正切值,值域在[-PI/2,PI/2]
static double toDegrees(double angrad) 将用孤度表示的角转换为近似相等的用角度表示的角
staticdouble toRadians(double angdeg) 将用角度表示的角转换为近似相等的用弧度表示的角
每个方法的参数和返回值都是double类型,参数以弧度代替角度来实现。
1.54指数运算
指数运算包括求根,取对数及求n次方的运算。在Math类中定义的指数运算方法如表所示:
方法 说明
static double exp(double a) 返回e的a次幂
static double pow(double a,double b) 返回以a为底数,以b为指数的幂值
static double sqrt(double a) 返回a的平方根
static double cbrt(double a) 返回a的立方根
static double log(double a) 返回a的自然对数,即lna的值
static double log10(double a) 返回以10为底a的对数
补充说明
//使用第二个浮点参数的符号返回第一个浮点参数。
System.out.println(Math.copySign(2.5,9.6));
//decrementExact(int a)返回一个递减1的参数,如果结果溢出int,则 int 。
System.out.println(Math.decrementExact(45));
//log(double a) 返回的自然对数(以 e为底) double值。
System.out.println(Math.log(6));
//multiplyExact(int x, int y)返回参数的乘积,如果结果溢出int,则抛出 int 。
System.out.println(Math.multiplyExact(5,6));
//nextDown(double d) 返回与负无穷大方向相邻的 d的浮点值。
System.out.println(Math.nextDown(3.5));
//rint(double a) 返回与参数最接近值的 double值,并且等于数学整数。
System.out.println(Math.rint(6));
(十九)Date与Calendar
1.Date类
Date类封装了当前日期和时间。位于java.util包。
(1)类构造方法
public Date() //使用当前日期和时间初始化对象
public Date(long date) //参数date为自从1970年1月1日子时经历的毫秒数
(2)常用方法
long getTime() | 返回从1970年1月1日午夜开始已经流逝毫秒数 |
void setTime(long time) | 将日期和时间设置为time所指定的值,time是是自从1970年1月1日午夜开始已经流逝的毫秒数 |
String toString() | 将调用Date对象转换成字符串并返回结果 |
(3)Date类示例
package com.bw;
import java.util.Date;
public class DateTest {
public static void main(String[] args) {
Date d = new Date();
System.out.println(d);
//System.out.println(d.toLocaleString());
long time1 = d.getTime();//1970-1-1 00:00:00 GMT 到现在的 总毫秒数
System.out.println(time1);
long time2 = System.currentTimeMillis();
System.out.println(time2);//1970-1-1 00:00:00 GMT 到现在的 总毫秒数
Date d2 = new Date(time2);
System.out.println(d2);
}
}
课后练习
package day09.teach;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class Test01 {
public static void main(String[] args) throws ParseException {
//时间类主要就是new新对象 改变时间 不然初始时间会被改变
Date date = new Date();
当前时间
System.out.println(date);
//当前毫秒数
long time = date.getTime();
//返回自1970年1月1日00:00:00 GMT 以来此Date对象表示的毫秒数
//时间类的格式化工具
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyy-MM-dd HH:mm:ss");
//把时间转换成字符串 format:格式化一个对象以生成一个字符串。
String format = simpleDateFormat.format(date);
System.out.println(format);//1970-01-01 08:14:15
//把字符串转换成时间
String str = "2003-04-12 08:08:08";
Date parse=simpleDateFormat.parse(str);
System.out.println(parse);
//计算出生到现在经历了多少毫秒 小时 月 之类的
//获取哦当前时间毫秒数
long time1 = date.getTime();
//获取出生时间毫秒数
long time2 = parse.getTime();
long a = time1-time2;
System.out.println(a);
System.out.println(a/1000);
System.out.println(a/1000/60/60/24/30);
}
}
2. Calendar与GregorianCalendar类
Calendar是抽象类,提供了一套方法将毫秒数形式的时间转换成大量有用的组分,例如年、月、日、小时、分和秒。
GregorianCalendar是Calendar的具体实现,它实现了您熟悉的常规的格林尼治日历。Calendar类的getInstance()方法通常会返回一个使用默认地区和时区下的当前时间初始化的GregorianCalendar对象。
3.SimpleDateFormat类
SimpleDateFormat 是个以与语言环境有关的方式来格式化和解析日期的具体类。它允许进行格式化(日期->文本),解析(文本 -> 日期)和规范化。
1.字符串转日期 2008-07-10 19:20:00要把它转成日期,可以用
SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
2. 日期转字符串假如把今天的日期转成字符串可用
sf.format(date )
3. 这个字符串内容的格式类似2008-07-10 19:20:00
String str = sdf.format(new Date());
4. jdk1.8日期类
关键类
Instant:// 时间戳 瞬时时间
时间戳(百度百科)
时间戳是指格林威治时间1970年01月01日00时00分00秒(北京时间1970年01月01日08时00分00秒)起至现在的总毫秒数。通俗的讲, 时间戳是一份能够表示一份数据在一个特定时间点已经存在的完整的可验证的数据。
LocalDate: // 同时含有年月日时分秒的日期时间对象 本地日期,不包含具体时间, 格式 yyyy-MM-dd。
LocalTime:// 只含有时分秒的时间对象 本地时间,不包含日期. 格式 yyyy-MM-dd HH:mm:ss.SSS 。
LocalDateTime: // 同时含有年月日时分秒的日期时间对象 组合了日期和时间,但不包含时差和时区信息。
ZonedDateTime:最完整的日期时间,包含时区和相对UTC或格林威治的时差。
示例
package day09.teach;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZonedDateTime;
import java.util.Calendar;
import java.util.Date;
public class Test02 {
public static void main(String[] args) {
//使用默认时区和语言环境获得一个日历
Calendar a = Calendar.getInstance();
System.out.println(a);
a.add(Calendar.DATE,1);
System.out.println(a);
//时间戳
//获取本地当前时间
LocalDate nowDate = LocalDate.now();
//本地日期包括分秒
LocalDateTime nowDateTime = LocalDateTime.now();
//最完整的日期时间,包含时区和相对UTC或格林威治的时差
ZonedDateTime now = ZonedDateTime.now();
System.out.println("当前时间"+nowDate);
System.out.println("当前时间为"+nowDateTime);
System.out.println("当前时间为"+now);
}
}
(二十)集合概述
(1)集合是存储其他对象的特殊对象。可以将集合当做一个容器。
(2)集合的相关接口和类位于java.util包中
(3)集合中的接口和类是一个整体、一个体系。
1.集合接口
接口定义了一组抽象方法,实现该接口的类需要实现这些抽象方法,从而实现接口的类就具备了接口所规定的行为(功能)。
集合框架定义了一些接口,它们决定了集合类的本质特性。具体的集合类只是提供了标准接口的不同实现。
接 口 | 描 述 |
Collection | 允许操作一组对象,它位于集合层次结构的顶部 |
List | 扩展Collection,以处理序列(对象列表) |
Set | 扩展Collection,以处理组,组中的元素必须唯一 |
注意:
Set类型的集合(实现Set接口的类)称为集,有些资料称为组,其特点是组中的元素必须唯一。
2. Collection接口
Collection接口是构建集合框架的基础,Collection是 泛型 接口,其声明如下:
interface Collection<E>,
//其中,E指定了集合将存储的对象类型。
提示:
集合中只能存储对象,不能存基本类型。
使用泛型的优点是在编译时可以检查元素的类型,从而更加安全。
Collection接口扩展了Iterable接口。这意味着所有集合都可以使用for-each风格的for循环进行遍历。
方 法 | 描 述 |
boolean add(E obj) | 将obj添加到调用集合。 |
boolean addAll(Collection<? extends E> c) | 将c中的所有元素添加到调用集合中。 |
boolean remove(Object obj) | 从调用集合中删除obj的一个实例。 |
boolean removeAll(Collection<?> c) | 从调用集合中删除c的所有元素。 |
void clear() | 删除调用集合中的所有元素 |
boolean contains(Object obj) | 如果obj是调用集合的元素,则返回true。 |
boolean isEmpty() | 如果调用集合为空,则返回true。 |
int size() | 返回调用集合中元素的数量 |
Iterator<E> iterator() | 返回调用集合的一个迭代器 |
containAll(Collection c) |
提示:
1、Collection接口中没有提供修改元素的方法。
2、Collection接口的父接口是Iterable接口,实现了Iterable接口的类是可以迭代的。
2. List接口
List接口扩展了Collection,并且声明了存储一连串元素的集合的行为。在列表中,可以使用从0开始的索引,通过它们的位置插入或访问元素。列表可以包含重复的元素。其声明如下:
interface List<E>
方 法 | 描 述 |
void add(int index, E obj) | 将obj插入到index所指定的位置。 |
boolean addAll(int index, Collection<?extends E> c) | 将c的所有元素插入到index所指定的位置。 |
E remove(int index) | 删除index位置的元素 |
E set(int index, E obj) | 将index所指定位置的元素设置为obj |
E get(int index) | 返回指定索引处存储的对象 |
int indexOf(Object obj) | 返回第一个obj实例的索引。 |
int lastIndexOf(Object obj) | 返回列表中最后一个obj实例的索引 |
ListIterator<E> listIterator() | 返回一个迭代器,该迭代器从列表的开头开始 |
List<E>subList(int start,int end) | 返回一个子列表。 |
注意:
List接口中操作元素的方法许多都提供了index参数,这是与Collection接口中所提供相关方法的主要区别。
3.Set接口
Set接口定义了组/集/集合(set)。它扩展了Collection接口,并声明了不允许重复元素的集合的行为。如果为集合添加重复的元素,add()方法会返回false。声明如下:
interface Set<E>
Set接口没有添加自己的方法。
SortedSet接口扩展了Set接口,并且声明了以升序进行排序的集合的行为。
interface SortedSet<E>
SortedSet定义了一些便于进行集合处理的方法。例如,为了获得集合中的第一个对象,可以调用first()方法。为了得到最后一个元素,可以使用last()方法。
NavigableSet接口扩展了SortedSet接口,并且该接口声明了支持基于最接近匹配原则检索元素的集合行为。
注意:
Set相关接口表示的集合没有索引的概念。
(二一)集合类
类 | 描 述 |
ArrayList | 动态数组 |
LinkedList | 链表 |
ArrayDeque | 双端队列 = 队列 + 堆栈 |
PriorityQueue | 支持基于优先级的队列 |
HashSet | 使用哈希表存储元素的组 |
LinkedHashSet | 扩展HashSet类,以允许按照插入的顺序进行迭代 |
TreeSet | 实现存储于树中的集合。 |
1.Arraylist类
ArrayList实现了List接口。本质上是元素为对象引用的长度可变的数组。
构造方法:
ArrayList( ) //长度取默认值 10
ArrayList(int capacity) //指定长度,容量
泛型:
T :代表一般的任何类。
E :代表 Element 元素的意思,或者 Exception 异常的意思。
K :代表 Key 的意思。
V :代表 Value 的意思,通常与 K 一起配合使用。
package day10.teach;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
public class Test01 {
public static void main(String[] args) {
//ArrayList可变长的复合类型数组
ArrayList arrayList = new ArrayList();
//添加 没有限度 只有字符型需要转换
arrayList.add(422);
arrayList.add("白熊");
arrayList.add('小白');
arrayList.add(new Date());
System.out.println(arrayList);
//查看指定位置的内容 索引从0开始 get 超出长度报错
System.out.println(arrayList.get(0));
//查看长度
System.out.println(arrayList.size());
//删除指定位置元素 删除索引 删除对象 两种
arrayList.remove(0);
System.out.println(arrayList);
//删除对象 转换
Character c = new Character('小白');
arrayList.remove(c);
System.out.println(arrayList);
//修改元素 修改时间相当于修改字符串
arrayList.set(01,"我是时间");
System.out.println(arrayList);
//指定位置添加
arrayList.add(0,"我是大白");
System.out.println(arrayList);
//查看是否为空
System.out.println(arrayList.isEmpty());//False
//清空
arrayList.clear();
System.out.println(arrayList);
System.out.println(arrayList.size());
//查看是否为空
System.out.println(arrayList.isEmpty());//True
ArrayList arrayList1 = new ArrayList();
arrayList1.add("我是另一个arrlist后添加的");
//相当于把后面的添加到前面
// arrayList.add(arrayList1);
// System.out.println(arrayList);//[[我是另一个arrlist后添加的]]
arrayList1.add(arrayList);
System.out.println(arrayList1);//[我是另一个arrlist后添加的, []]
}
}
示例
ArrayList arrayList = new ArrayList();
System.out.println(arrayList);
//相当于默认初始化属性
List list = new ArrayList(arrayList);
System.out.println(list);
list.add(222);
Test03 n = new Test03();
n.add();
System.out.println(n);
n.add(list);
System.out.println(n);
n.remove(0);
System.out.println(list);
2. LinkedList类
LinkedList类实现了List、Deque以及Queue接口。它提供了(双向)链表数据结构。
LinkedList具有两个构造方法:
LinkedList( )
LinkedList(Collection<? extends E> c)
3.ArrayList与LinkedList区别
1、ArrayList是基于数组结构的集合,是动态数组,有容量的概念;LinkedList是基于链表结构的集合,没有容量的概念 Vector 与ArrayList对等类。
2、对于 随机访问(get和set方法),ArrayList优于LinkedList,因为LinkedList要移动指针。
3、对于 新增和删除操作(add和remove方法),LinkedList比较占优势,因为ArrayList要移动数据。但是如果只是在末尾追加元素,并且没有超出容量限制,则ArrayList的性能更好。不关容量的情况,效率差不多。
4、LinkedList 还实现了Queue接口,该接口比List提供了更多的方法,包括 offer(),peek(),poll()等。
示例
//泛型 约束 尖括号 引用类型 不能是基本数据类型
//一个反省只能约束,定义,检查一种类型
//尖括号里放想要的 List<User> list = new ArrayList();
List<Integer> list = new ArrayList();
Book book = new Book();
list.add(123);
list.add(68);
练习
LinkedList list = new LinkedList();
list.add(123);
list.add("456");
list.add('m');
list.add(new Date());
System.out.println(list);
System.out.println(list.size());
list.addFirst(555);
list.addLast(222);
System.out.println(list);
list.add(0,"我是白熊");
System.out.println(list);
list.clone();
//返回此 LinkedList 的浅表副本。
System.out.println(list+"++++");
//移除并返回此列表的第一个元素。
list.removeLast();
list.removeFirst();
System.out.println(list);
list.remove(1);
System.out.println(list);
Object o = new Object();
o=123;
//从此列表中移除首次出现的指定元素(如果存在)。
list.remove(o);
System.out.println(list);
//注意是字符串还是啥类型
System.out.println(list.contains("456"));
//获取但不移除此列表的头(第一个元素)
System.out.println(list.element());
//从此列表中移除第一次出现的指定元素(从头部到尾部遍历列表时)。
System.out.println(list.removeFirstOccurrence(555));
list.add(111);
System.out.println(list);
//返回此列表中指定位置处的元素
System.out.println(list.get(1));
//返回此列表中首次出现的指定元素的索引,如果此列表中不包含该元素,则返回 -1。
System.out.println(list.indexOf(111));//0
//返回此列表中最后出现的指定元素的索引,如果此列表中不包含该元素,则返回 -1。
System.out.println(list.lastIndexOf(111));//4
// 将指定元素添加到此列表的末尾(最后一个元素)。
list.offer(999);
System.out.println(list);
list.offerFirst(0);
list.offerLast(888);
System.out.println(list);
//获取但不移除此列表的头(第一个元素)。 peekFirst peekLast 此列表为空,则返回 null
System.out.println(list.peek());
//获取并移除此列表的头(第一个元素) pollFirst pollLast
System.out.println(list.poll());
System.out.println(list);
//从此列表所表示的堆栈处弹出一个元素。 第一个元素
System.out.println(list.pop());
//将元素推入此列表所表示的堆栈。 第一个元素
list.push(333);
System.out.println(list);
//将此列表中指定位置的元素替换为指定的元素。
list.set(1,"我是白熊");
System.out.println(list);
System.out.println(list.toArray());
}
3.HashSet
HashSet类实现了Set接口。该类在内部使用哈希表存储元素。
哈希表使用称之为散列法(hashing)的机制存储信息。哈希法的优点是add()、contains()、remove()以及size()方法的执行时间保持不变,即使是对于比较大的集合也是如此。
HashSet( ) //默认容量是16
HashSet(int capacity)
HashSet(int capacity, float fillRatio) //填充率:0.0-1.0之间,默认0.75
HashSet(Collection<? extends E> c)
HashSet中元素不是按有序的顺序存储的,遍历输出HashSet中的元素时精确的输出可能不同。
示例
//无序存放
HashSet set = new HashSet();
set.add(123);
set.add("白熊");
set.add('a');
set.add(321);
//输出也是无序 [a, 321, 白熊, 123]
System.out.println(set);
System.out.println(set.size());
//删除 只能删除对象 没有下标
set.remove(321);
System.out.println(set);
//只能直接查找指定内容 不能遍历
System.out.println(set.contains(123));
//比较指定对象与此 set 的相等性。
System.out.println(set.equals(456));
//返回 set 的哈希码值。
System.out.println(set.hashCode());
System.out.println(set.isEmpty());
//返回此 HashSet 实例的浅表副本:并没有复制这些元素本身。
System.out.println(set.clone());
4.LinkedHashSet类
此类提供所有可选的 Set 操作,并且允许 null 元素。与 HashSet 一样,它可以为基本操作(add、contains 和 remove)提供稳定的性能,假定哈希函数将元素正确地分布到存储段中。
LinkedHashSet类扩展了HashSet类,它没有添加它自己的方法。
LinkedHashSet在内部使用一个链表维护元素添加到集合中的顺序,因此可以按照插入顺序迭代集合。
示例
//LinkedHashSet : 在HashSet基础上 按照输入顺序输出 其他方法也一样
LinkedHashSet lst = new LinkedHashSet();
lst.add(123);
lst.add("白熊");
lst.add('a');
lst.add(456);
// 在HashSet基础上 按照顺序输出
System.out.println(lst);
lst.remove(123);
System.out.println(lst);
5.TreeSet类
TreeSet类实现了NavigableSet接口,该类在内部使用树结构存储元素。元素以升序存储,访问和检索相当快。TreeSet适合于存储大量的、必须能够快速查找到的有序信息。
5.1 Set与List的区别
1、Set中的元素无序不能重复,List中的有序元素可以重复。
2、List有索引(下标)的概念,Set没有索引的概念。
3、对于Set表示的集合,通常是遍历操作,没有get()和set()方法。
注意:
TreeSet以升序保存对象,所以TreeSet中保存的对象比较能够比较大小,即TreeSet保存的对象类型必须实现Comparable接口。
注意:
HashSet是无序的,LinkedHashSet和TreeSet是有序的。
示例
//在HashSet基础上 按照升序输出 其他方法一样
TreeSet tst = new TreeSet<>();
//只能是输入数字之后升序排序 字符字符串都不行
tst.add(123);
//tst.add("白熊");
//tst.add('a');
tst.add(555);
tst.add(99);
tst.add(456);
//在HashSet基础上 按照升序输出 [99, 123, 456, 555]
System.out.println(tst);
6.集合遍历
6.1 Iterable接口
实现了Iterable接口的类是可以遍历的。因为Iterable接口是Collection接口的父接口,而所有集合类都实现了Collection接口,从而也都实现了Iterable接口,所以所有集合类都是可以遍历的。
Iterable接口只定义了一个方法:
Iterator<T> iterator() //返回迭代器对象
既然所有集合都实现了Iterable接口,所以所有集合类都重写了iterator()方法以返回一个迭代器对象。
6.2 Iterator接口
Iterator接口描述了迭代器的行为,所有迭代器类都必须实现该接口,该接口定义了一下方法:
boolean hasNext() 如果迭代还有更多的元素则返回true
T next() 返回下一个元素
void remove() 删除迭代器返回的元素
提示:
从Iterator接口定义的方法不难看出Iterator只能从前向后进行遍历。
6.3 ListIterator接口
Iterator接口有一个子接口ListIterator,ListIterator接口即可以从前向后遍历,也可以从后向前遍历集合。只有实现了List接口的集合类才提供了ListIterator迭代器。
List接口提供以下两个方法用于获取列表集合的列表迭代器:
ListIterator<E> listIterator() 该迭代器从列表的开头开始
ListIterator<E> listIterator()(int index) 该迭代器从index所指定的位置开始
ListIterator接口定义了以下常用方法。
boolean hasNext()
Boolean hasPrevious()
E next()
int nextIndex()
E previous()
int previousIndex()
6.4 使用迭代器
通常,为了使用迭代器遍历集合的内容,需要以下步骤:
1.通过调用集合的Iterator()方法,获取指向集合开头的迭代器。
2.建立一个hasNext()调用循环。只要hasNext()返回true,就循环迭代。
3.在循环中,通过调用next()获取每个元素。
对于实现了List接口的集合,还可以调用listIterator()获取迭代器。列表迭代器提供了向前和向后两个方向访问集合的能力,并且允许修改元素。
6.5 增强的for循环
如果不修改集合的内容,也不以反向获取元素,则使用 for-each 版的for循环遍历集合通常比使用迭代器更方便。
- 迭代器与增强的for循环之间的区别:
使用迭代器遍历集合时,可以调用 Iterator.remove() 方法删除集合中元素,使用增强的for循环遍历集合时,不能删除集合中的元素。
可以使用增强的for循环遍历数组,但是数组不支持迭代器。
使用增强的for循环遍历基本类型的数组时,只能使用数组元素,而不能修改数组元素。
7.Collections工具类
Collections类里面包括动态、有序、可变大小的一维数组Vector与ArrayList
7.1对List进行排序操作
Collections提供以下方法对List进行排序操作:
void reverse(List list):反转
void shuffle(List list),随机排序
void sort(List list),按自然排序的升序排序
void sort(List list, Comparator c);定制排序,由Comparator控制排序逻辑
void swap(List list, int i , int j),交换两个索引位置的元素
7.2遗留的集合类和接口
在集合之前,Java提供了特定的类,存储和管理对象组,例如Dictionary、Vector、Stack和Properties。
早期版本的java.util包没有包含集合框架。反而,定义了几个类和接口,提供存储对象的专门方法。当添加集合时(由J2SE 1.2添加),对几个原始类进行了重新设计,以支持集合接口。因此,从技术上讲它们现在是集合框架的组成部分。
前面介绍的所有现代集合类都不是同步的,但是所有遗留类都是同步的。
7.3 Vector类
现在Vector类实现了动态数组,与ArrayList类似,也实现List接口。
Vector与ArrayList有如下区别:
- Vector实现同步,线程安全。ArrayList没有实现线程安全。
- Vector性能比ArrayList低。
- Vector和ArrayList在更多元素添加进来时会请求更大的空间。Vector每次请求其大小的双倍空间,而ArrayList每次对size增长50%.
8.Map
映射(map)是存储键和值间关联(即,键值对)的对象。给定一个键,可以找到其值。键和值都是对象。键必须唯一,但是值可以重复。
1. 支持映射的接口
接 口 | 描 述 |
Map | 将唯一键映射到值 |
NavigableMap | 扩展SortedMap接口,以处理基于最接近匹配搜索原则的键/值对检索 |
SortedMap | 扩展Map接口,从而以升序保存键 |
Map.Entry | 描述映射中的元素(键/值对) |
interface Map<K, V>
映射围绕两个基本操作:get()和put()。为了将值放入到映射中,使用put(),指定键和值。为了获取值,调用get(),传递键作为变元,值会被返回。
2.Map实现类
常用的映射类:HashMap、LinkedHashMap、TreeMap
2.1 HashMap
HashMap实现了Map接口。它使用哈希表存储映射,即使对于比较大的集合,get()和put()的执行时间也保持不变。其声明如下:
class HashMap<K, V>
HashMap类定义了以下构造方法:
HashMap( )
HashMap(int capacity) //指定容量,默认是16
HashMap(int capacity, float fillRatio) //充填比率,0.0-1.0之间,默认0.75
HashMap(Map<? extends K, ? extends V> m)
示例
package day12.teach;
import java.util.*;
/**
* @Author: 白熊
* @Date: 2024/2/23 9:31
* Map --> HashMap
* HashMap是Java中的一种键值对集合,它允许存储不重复的键和对应的值
* 常见的Map实现类有:HashMap、TreeMap、LinkedHashMap等。
*/
public class Test01 {
public static void main(String[] args) {
Map map = new HashMap();
//键不能重复 若添加的键已存在,会覆盖值 ---> 引申为修改
//put(键,值) 将指定的值与此映射中的指定键关联(可选操作)。
map.put("1","11111");
map.put("1","2222");
map.put("1","3333");
map.put('2',"11111");
map.put("3","5556");
map.put("4","6666");
map.put("5","8888");
//可以输出
map.put(null,"8888");
map.put("6",null);
Map<String, Integer> map1 = new HashMap<>();
map1.put("1",1111);
map1.put("2",222);
System.out.println(map);
//取值 键可以是任何基本数据类型
System.out.println(map.get("1"));
//条数 4
System.out.println(map.size());
//判断是否为空
System.out.println(map.isEmpty());
//修改
map.put("3","9999");
System.out.println(map);
//删除 删除键,值就不在
map.remove("1");
System.out.println(map);
//判断是否包含指定的键
System.out.println(map.containsKey("3"));
//判断是否包含指定的值
System.out.println(map.containsValue("999"));
//清空 clear();
System.out.println(map.values());//返回所有值
//遍历 先遍历键 再找值
/* //使用keyset去除所有键 存储到set集合
Set<String> strings = map1.keySet();
//使用迭代器遍历set集合 获取每一个key
Iterator<String> it = strings.iterator();
while (it.hasNext()){
String key = it.next();
//get()方法 通过key找到value
Integer value = map1.get(key);
System.out.println(key+"="+value);
}*/
//返回此映射中包含的键的 Set 视图
//获取Map的keySet():通过遍历key,获取对应的value,从而遍历键值对:
for(String key:map1.keySet()){//遍历Map集合的keyset集合
Integer value= map1.get(key);//获取value
System.out.println("遍历map集合键值对"+key+"="+value);
}
//获取Map集合的Entryset(),遍历Map的Entry集合,直接获取键值对对象
for (Map.Entry<String,Integer> entry:map1.entrySet()){
System.out.println(entry);
}
System.out.println();
//使用迭代器:
Iterator<Map.Entry<String,Integer>> it = map1.entrySet().iterator();
while (it.hasNext()){
System.out.println(it.next());
}
System.out.println();
Collection<String> values = map.values();
for (String str:values){
System.out.println(str);
}
Set entries = map.entrySet();
System.out.println(entries);
}
}
2.2LinkedHashMap
示例
package day12.teach;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.Map;
/**
* @Author: 白熊
* @Date: 2024/2/23 10:54
* LinkedHashMap
* LinkedHashMap是HashMap的一个子类 可以用 Map lsm = new LinkedHashMap();
* 它可以维护插入元素时的顺序
*/
public class Test03 {
public static void main(String[] args) {
//LinkedHashMap lsm = new LinkedHashMap();
//Map<String,String> lsm = new LinkedHashMap<>();
Map lsm = new LinkedHashMap();
lsm.put("1","789");
lsm.put("2","789");
lsm.put("3","789");
System.out.println(lsm);
}
}
2.3TreeMap
package day12.teach;
import java.util.SortedMap;
import java.util.TreeMap;
/**
* @Author: 白熊
* @Date: 2024/2/23 11:30
*TreeHashMap TreeMap是有序的,但它并不是线程安全的
* TreeMap是Java中的一种有序键值对集合
*/
public class Test04 {
public static void main(String[] args) {
//TreeMap tmp = new TreeMap();
TreeMap<String,String> tmp = new TreeMap<>();
//Map tmp = new HashMap();
tmp.put("1","8888");
tmp.put("2","777");
tmp.put("3","666");
tmp.put("4","5555");
System.out.println(tmp);
System.out.println(tmp.get("1"));
tmp.remove("4");
System.out.println(tmp);
//假如没有int类型的键 不会输出false 会直接报错
System.out.println(tmp.containsKey("4"));
System.out.println(tmp.containsValue("8888"));
System.out.println(tmp.firstKey());
System.out.println(tmp.lastKey());
//返回部分子Map
SortedMap<String,String> sub = tmp.subMap("1",true,"3",false);
System.out.println(sub);
}
}
2.3.HashTable
Hashtable类也实现了Map接口,与HashMap类似。
主要区别:
1、Hashtable是同步的、线程安全的,而HashMap不是同步 👩🏻🤝👩🏻 的,没有实现线程安全。
2、HashMap允许将null作为一个条目的key或者value,而 Hashtable不允许 。当然只能有一个条目的键为null。
示例
package day12.teach;
import java.util.Hashtable;
/**
* @Author: 白熊
* @Date: 2024/2/23 13:37
* HashMap HashTable 区别
* HashMap null 可以作为键,这样的键只有一个(键名不重复 否则是修改);可以有一个或多个键所对应的值为 null 。
* HashTable key 和 value 都不允许出现 null 值
*/
public class Test05 {
public static void main(String[] args) {
Hashtable htb = new Hashtable();
htb.put("1",111);
htb.put("2",111);
// key 和 value 都不允许出现 null 值
//htb.put(null,111);
//htb.put("1",null);
System.out.println(htb);
}
}
(二二)异常Exception
1. 概念
异常也就是非正常情况,比如使用空的引用、数组下标越界、内存溢出错误等,这些都是意外的情况,背离我们程序本身的意图。
Java提供了异常对象描述这类异常情况。
Java提供了异常机制来进行处理,通过异常机制来处理程序运行期间出现的错误。通过异常机制,可以更好地提升程序的健壮性。
可以将异常通俗的理解为“陷阱”,程序运行出现异常,就是掉到陷阱了。
注意:
Java异常是描述在一块代码中发生的异常情况(也就是错误)的对象。
2.异常处理基本流程
第一种情况:
try{
}
catch(…){
}
大部分设计良好的catch子句,应当能够分辨出异常情况,然后继续执行,就好像错误根本没有发生一样。
异常处理过程:
(1)当执行过程中遇到异常时,系统会抛出异常(对象)
(2)catch()块捕获异常并处理。
(3)没有被捕获的异常最终都将由默认处理程序进行处理。默认处理程序会显示一个描述异常的字符串,输出异常发生点的跟踪栈,并终止程序。
提示:
由try保护的语句必须使用花括号括起来(即,它们必须位于一个块中)。不能为单条语句使用try。catch块也不能省略花括号。
try块中发生异常之后直接进入catch块,执行完catch块后也不会再返回到try块。因此,try块中发生异常之后的语句都不会再执行。
第二种情况:多条catch子句
try{
}
catch(…){
}
catch(…){
}
提示:
try及其catch语句构成了一个单元。catch子句的作用域被限制在由之前try语句指定的那些语句。
执行了一条catch语句之后,会忽略其他catch语句,并继续执行try/catch块后面的代码。
当使用多条catch语句时,要重点记住异常子类必需位于它的所有超类之前。
第三种情况:finally
try(){
}
/*可以有多个catch块*/
finally{
}
提示:
不管是否有异常抛出,都会执行finally块。
最多只能有一个finally块。
对于关闭文件句柄、以及释放资源,finally子句都是很有用的。
第四种情况:
try(){
}
catch(…){
}
catch(…){
}…
finally{
}
提示:
每个try语句至少需要有一个catch子句或一个finally子句。
3. 嵌套的try语句
一条try语句可以位于另外一条try语句中。
如果内层的try语句没有为特定的异常提供catch处理程序,执行流程就会跳出该try语句,检查外层try语句的catch处理程序,查看异常是否匹配。这个过程会一直继续下去,直到找到了一条匹配的catch语句,或直到检查完所有的try语句。如果没有找到匹配的catch语句,则Java运行时系统会处理该异常。
提示:
当涉及方法调用时,可以能会出现不那么明显的try语句嵌套。
4.异常类型
4.1 异常继承体系
Error一般指与JVM相关的错误,如系统崩溃、虚拟机错误、动态链接失败等。
Exception有一个重要子类RuntimeException。所有RuntimeException类及其子类的实例被称为运行时(Runtime)异常。对于您编写的程序而言,这种类型的异常是自动定义的,包括除零和无效数组索引这类情况。
运行时异常之外,称为非运行时异常/编译异常。
Throwable重写了(由Object定义的)toString()方法,从而可以返回一个包含异常描述的字符串。可以使用println()语句显示这个描述。
4.2 异常分类
- Java的异常分为两大类:checked异常和unchecked异常。
- unchecked异常(非检查异常),也称运行时异常(RuntimeException),比如常见的NullPointerException、IndexOutOfBoundsException。对于运行时异常,java编译器不要求必须进行异常捕获处理或者抛出声明,由程序员自行决定。运行异常
- checked异常(检查异常),也称非运行时异常(运行时异常以外的异常就是非运行时异常)。Java认为Checked异常都是可以被处理(修复)的异常,所以Java程序必须显式处理Checked异常,否则无法通过编译。编译异常
- 含义:checked异常是指程序员比较进行检查,必须进行处理。
4.对于Checked异常的处理方式有如下两种:
- 当前方法明确知道如何处理该异常,则应该使用try…catch块来捕获该异常,然后在对应的块中修复该异常。
- 当前方法不知道如何处理这种异常,则应该在定义该方法时声明抛出该异常。
4.3 常用异常
unchecked异常 运行时异常
要求:看到异常类名,要知道表示哪种错误,知道属于哪类异常
java.lang包中定义的unchecked异常
异 常 | 含 义 |
ArithmeticException | 算术错误,例如除零 |
ArrayIndexOutOfBoundsException | 数组索引越界 |
ArrayStoreException | 使用不兼容的类型为数组元素赋值 |
ClassCastException | 无效的转换 |
EnumConstantNotPresentException | 试图使用未定义的枚举值 |
IllegalArgumentException | 使用非法实参调用方法 |
IllegalMonitorStateException | 非法的监视操作,例如等待未锁定的线程 |
IllegalStateException | 环境或应用程序处于不正确的状态 |
IllegalThreadStateException | 请求的操作与当前线程状态不兼容 |
IndexOutOfBoundsException | 某些类型的索引越界 |
NegativeArraySizeException | 使用负数长度创建数组 |
NullPointerException | 非法使用空引用 |
NumberFromatException | 字符串到数字格式的无效转换 |
SecurityException | 试图违反安全性 |
StringIndexOutOfBounds | 试图在字符串边界之外进行索引 |
TypeNotPresentExcepton | 未找到类型 |
UnsupportedOpetationException | 遇到一个不支持的操作 |
示例
package day13.teach;
import day11.homework.beans.Book;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* @Author: 白熊
* @Date: 2024/2/26 11:16
*/
public class ExceptionTest {
public static void main(String[] args) {
add();
System.out.println("运行完成");
}
public static void add(){
// try{
//}catch (Exception e){
//运行异常
Book book = null;
int id = book.getId();
System.out.println(id);
//编译异常 能看见的直接的异常 必须要处理的
//假如不处理 就抛出 谁调用谁处理
SimpleDateFormat sml = new SimpleDateFormat();
String strv= "";
//Date p = sml.parse(strv);//直接能看到的 会爆红
// }
}
}
4.4常用的checked异常 编译
IOExeption //输入、输出异常
FileNotFoundException //文件不存在异常
SQLException //SQL异常
java.lang包中定义的Checked异常(了解)
异 常 | 含 义 |
ClassNotFoundException | 未找到类 |
CloneNotSupportedException | 试图复制没有实现Cloneable接口的对象 |
IllegalAccessException | 对类的访问被拒绝 |
InstantiationException | 试图为抽象类或接口创建对象 |
InterruptedException | 一个线程被另一个线程中断 |
NoSuchFieldException | 请求的字段不存在 |
NoSuchMethodException | 请求的方法不存在 |
ReflectiveOperationException | 与反射相关的异常的子类(该异常是由JDK 7新增的) |
4.5 throw
也可以使用throw语句显式地抛出一个异常。
在throw语句之后的执行流会立即停止,所有后续语句都不会执行。然后检查最近的try块,查看是否存在和异常类型相匹配的catch语句。
4.6 throws
如果方法可能引发一个Checked异常,则必须在方法声明中提供throws子句列出了方法可能抛出的异常类型,从而方法的调用者能够保卫它们自己以防备该异常。
4.7throw和throws区别
- throw抛异常对象,应用在代码块内
- throws声明可能抛出的异常类型,在方法定义后面。
- 如果方法内使用throw抛出Checked异常对象,又没有进行try catch处理,则该方法定义同时需要使用throws指明抛出异常类型
示例
package day13.teach;
import day11.homework.beans.Book;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* @Author: 白熊
* @Date: 2024/2/26 11:08
* throws 声明抛出异常 让别人处理
* throw 抛出异常不处理
* 运行异常(自己写try-catch-复制框框的) 编译异常(Alt+回车)
* 异常一定要自己处理 不要抛出去
*/
public class Test01 {
public static void main(String[] args) {
//运行异常没办法处理,输入异常必须手动处理
int i = 10;
//java.lang.ArithmeticException: / by zero
try{
System.out.println(i/0);
//System.out.println(i/1);
}catch (ArithmeticException e ){
e.printStackTrace();
}
//System.out.println(i/1);
Book book = null;
//Cannot invoke "com.xszx.beans.Book.getId()" because "book" is null
//System.out.println(book.getId());
SimpleDateFormat sml = new SimpleDateFormat();
String strv= "";
//Error:(22, 27) java: 未报告的异常错误java.text.ParseException; 必须对其进行捕获或声明以便抛出
//捕获检查
//多重处理情况
try {
Date p = sml.parse(strv);
System.out.println(i/0);
System.out.println(i/1);
//单独
//Exception 所有异常的父类 父类写了之后后面不能抛出它的子类
//写到所有子异常后面可以
} catch (ParseException e){//处理
//打印输出 继续执行代码 不影响代码运行
e.printStackTrace();
//单独
}catch (ArithmeticException e){
e.printStackTrace();
}finally {
//始终都会执行 不管是否有异常抛出 都会执行
System.out.println("我是大白,始终都执行");
}
System.out.println("qwert");
}
}
(二三)File类
File对象既可以表示一个文件,也可以表示一个路径/目录。
1. 创建File对象
File对象描述了文件/目录本身的属性。File对象用于获取和操作与磁盘文件/目录关联的信息,例如权限、时间、日期以及目录路径,并且还可以浏览子目录层次。
File(String directoryPath)
File(String directoryPath, String filename)
File(File dirObj, String filename)
File(URI uriObj)
2. File类的常用方法
boolean exists() //File对象所表示的文件或目录是否存在
String getName() //获取文件名或路径名
String [getPath](#getPath())() //将此抽象路径名转换为一个路径名字符串
String getAbsolutePath()
boolean isFile() //测试此抽象路径名表示的文件是否是一个标准文件。
boolean isDirectory() //测试此抽象路径名表示的文件是否是一个目录。
boolean createNewFile() 创建新文件,只能创建文件,如果目录不存在,则异常
boolean mkdir() 只能创建一层目录 make dirctory
boolean mkdirs() 可以创建多层目录
boolean [delete](#delete())() 删除文件或文件夹(要求文件夹为空)
File[ ] listFiles() 返回值类型为
String[] list();
boolean canWrite() 判断文件对象表示的文件/目录是否可以写入
boolean canRead() 判断文件对象表示的文件/目录是否可以读取
long length() 返回文件的长度
File类中重写了equals方法,比较的文件路径。
示例
package day13.teach;
import java.io.File;
import java.io.IOException;
/**
* @Author: 白熊
* @Date: 2024/2/26 14:23
* File类 -->文件类
*/
public class Test02 {
public static void main(String[] args) throws IOException {
/**
* File(File parent, String child)
* 根据 parent 抽象路径名和 child 路径名字符串创建一个新 File 实例。
* File(String pathname)
* 通过将给定路径名字符串转换为抽象路径名来创建一个新 File 实例。
* File(String parent, String child)
* 根据 parent 路径名字符串和 child 路径名字符串创建一个新 File 实例。
* File(URI uri)
* 通过将给定的 file: URI 转换为一个抽象路径名来创建一个新的 File 实例。
*/
//文件夹
File file = new File("F:\\个人资料\\水课论文\\");
//记得后缀 文件
File file1 = new File("F:\\个人资料\\水课论文\\2022005608-白熊-《艺术不需要规则》-初稿.docx\\");
//文件目录是否存在
System.out.println(file.exists());//true
//
System.out.println(file.getName());
//canExecute()测试应用程序是否可以执行此抽象路径名表示的文件。
//canRead()读 canWrite()修改
System.out.println(file.canExecute());//true
// 按字母顺序比较两个抽象路径名。
System.out.println(file.compareTo(file1));//-33
//createNewFile() 当且仅当不存在具有此抽象路径名指定名称的文件时,不可分地创建一个新的空文件。
//前提是不存在才能创建 此时输出true 已经存在的输出显示为false
File file2 = new File("D:\\aaa");
System.out.println(file2.createNewFile());
//createTempFile(String prefix, String suffix) 在默认临时文件目录中创建一个空文件,使用给定前缀和后缀生成其名称。
System.out.println(file2.createTempFile("D:\\aaa",".docx"));
System.out.println(file2.delete());//true表示删除成功
//equals(Object obj)
//测试此抽象路径名与给定对象是否相等。
}
}
(二四)IO流
1.字节流
1.1 字节输入流InputStream
read():从此输入流中读取一个数据字节。
read(byte[] b):从此输入流中将最多b.length个字节的数据读入一个byte数组中。
read(byte[] b, intoff, int len) :从此输入流中将最多 len 个字节的数据读入一个 byte 数组中。
close() :关闭此输入流并释放与该流关联的所有系统资源。
1.2 字节输出流OutputStream
write(byte[] b) :将 b.length 个字节从指定 byte 数组写入此文件输出流中。
write(byte[] b, intoff, int len) :将指定 byte 数组中从偏移量 off 开始的 len 个字节写入此文件输 出流。
write(intb) :将指定字节写入此文件输出流。
close() :关闭此输入流并释放与该流关联的所有系统资源。
示例
//字节流的方式效率较低,不建议使用
public class IOTest {
public static void main(String[] args) throws IOException {
File file = new File("D:/test.txt");
write(file);
System.out.println(read(file));
}
public static void write(File file) throws IOException {
OutputStream os = new FileOutputStream(file, true);
// 要写入的字符串
String string = "松下问童子,言师采药去。只在此山中,云深不知处。";
// 写入文件
os.write(string.getBytes());
// 关闭流
os.close();
}
public static String read(File file) throws IOException {
InputStream in = new FileInputStream(file);
// 一次性取多少个字节
byte[] bytes = new byte[1024];
// 用来接收读取的字节数组
StringBuilder sb = new StringBuilder();
// 读取到的字节数组长度,为-1时表示没有数据
int length = 0;
// 循环取数据
while ((length = in.read(bytes)) != -1) {
// 将读取的内容转换成字符串
sb.append(new String(bytes, 0, length));
}
// 关闭流
in.close();
return sb.toString();
}
}
BufferedInputStream、BufferedOutputStream(缓冲字节流)
//缓冲字节流是为高效率而设计的,真正的读写操作还是靠FileOutputStream和FileInputStream,所以 其构造方法入参是这两个类的对象也就不奇怪了。
public class IOTest {
public static void write(File file) throws IOException {
//缓冲字节流,提高了效率
BufferedOutputStream bis = new BufferedOutputStream(new
FileOutputStream(file, true));
// 要写入的字符串
String string = "松下问童子,言师采药去。只在此山中,云深不知处。";
// 写入文件
bis.write(string.getBytes());
// 关闭流
bis.close();
}
public static String read(File file) throws IOException {
BufferedInputStream fis = new BufferedInputStream(new
FileInputStream(file));
// 一次性取多少个字节
byte[] bytes = new byte[1024];
// 用来接收读取的字节数组
StringBuilder sb = new StringBuilder();
// 读取到的字节数组长度,为-1时表示没有数据
int length = 0;
// 循环取数据
while ((length = fis.read(bytes)) != -1) {
// 将读取的内容转换成字符串
sb.append(new String(bytes, 0, length));
}
// 关闭流
fis.close();
return sb.toString();
}
}
2.字符流
2.1 字符输入流Reader
read():读取单个字符。
read(char[] cbuf) :将字符读入数组。
read(char[] cbuf, intoff, int len) : 将字符读入数组的某一部分。
read(CharBuffer target) :试图将字符读入指定的字符缓冲区。
flush() :刷新该流的缓冲。
close() :关闭此流,但要先刷新它。
2.2 字符输出流Writer
write(char[] cbuf) :写入字符数组。
write(char[] cbuf, intoff, int len) :写入字符数组的某一部分。
write(intc) :写入单个字符。
write(Stringstr) :写入字符串。
write(String str, intoff, int len) :写入字符串的某一部分。
flush() :刷新该流的缓冲。
close() :关闭此流,但要先刷新它。
2.3字节流字符流区别
字节流一般用来处理图像、视频、音频、PPT、Word等类型的文件。字符流一般用于处理纯文本类型的文件,如TXT文件等,但不能处理图像视频等非文本文件。用一句话说就是:字节流可以处理一切文件,而字符流只能处理纯文本文件。
1.word.doc 数据字节流还是字符流?答:.doc数据字节流。2.Excel 数据字节流还是字符流?答:要根据保存的格式进行判断,如果是保存为.csv那么他就是字符流,如果是其他的则数据字节流。
- 字符流是以Reader,Writer结尾的
- 字符节流是以InputStream或OutputStream结尾的
3.字符缓冲流
BufferedWriter类 newLine() :写入一个行分隔符。这个方法会自动适配所在系统的行分隔符。
BufferedReader类 readLine() :读取一个文本行。
字符流适用于文本文件的读写 , OutputStreamWriter类其实也是借助 FileOutputStream类实现 的,故其构造方法是FileOutputStream 的对象。
示例
public class IOTest {
public static void write(File file) throws IOException {
// OutputStreamWriter可以显示指定字符集,否则使用默认字符集
OutputStreamWriter osw = new OutputStreamWriter(new
FileOutputStream(file, true), "UTF-8");
// 要写入的字符串
String string = "松下问童子,言师采药去。只在此山中,云深不知处。"; osw.write(string);
osw.close();
}
public static String read(File file) throws IOException {
InputStreamReader isr = new InputStreamReader(new FileInputStream(file), "UTF-8");
// 字符数组:一次读取多少个字符
char[] chars = new char[1024];
// 每次读取的字符数组先append到StringBuilder中
StringBuilder sb = new StringBuilder();
// 读取到的字符数组长度,为-1时表示没有数据
int length;
// 循环取数据
while ((length = isr.read(chars)) != -1) {
// 将读取的内容转换成字符串
sb.append(chars, 0, length);
}
// 关闭流
isr.close();
return sb.toString()
}
}
//缓冲字符流
public static void write(File file) throws IOException {
// BufferedWriter fw = new BufferedWriter(new OutputStreamWriter(new // FileOutputStream(file, true), "UTF-8"));
// FileWriter可以大幅度简化代码
BufferedWriter bw = new BufferedWriter(new FileWriter(file, true));
// 要写入的字符串
String string = "松下问童子,言师采药去。只在此山中,云深不知处。";
bw.write(string);
bw.close();
}
public static String read(File file) throws IOException {
BufferedReader br = new BufferedReader(new FileReader(file));
// 用来接收读取的字节数组
StringBuilder sb = new StringBuilder();
// 按行读数据
String line;
// 循环取数据 readLine读取一行
while ((line = br.readLine()) != null) {
// 将读取的内容转换成字符串
sb.append(line);
}
// 关闭流
br.close();
return sb.toString();
}
(二五)反射
1.反射的概念
反射是在运行时获取类或对象的信息的能力。具体的讲:可以通过类名或对象获取该类的相关信息, 例如类的属性和方法、类实现接口或继承父类。甚至可以通过反射在运行过程中实例化对象,动态调用 方法。
反射是在运行过程中进行检索/检查自身的一个机制。
反射是通过Class、Constructor、 Field以及Method类实现的。这些类位java.lang.reflect包下。
2.类加载与Class对象
在程序中使用某个类时, JVM首先需要将该类的class文件加载到内存,并为之创建一个java.lang.Class类型的对象。这个对象就是所谓的Class对象,通过该对象就可以获取该类的相关信息。
在程序中使用某个类时, JVM首先需要将该类的class文件加载到内存,并为之创建一个java.lang.Class 类型的对象。这个对象就是所谓的Class对象,通过该对象就可以获取该类的相关信息。
2.1获取反射的三种方式
可以通过以下三种方式获取类的Class对象:
类名.class //大部分时候使用这种方式
对象.getClass()
Class.forName("类的全名") //参数必须是类的全名
一旦获得了类的class对象,就可以通过该Class对象获取类的相关信息了.
3.获取类的相关信息
通过类的Class对象,可以获取类的属性、方法、构造方法等信息,这些信息分别用Field、 Method、Constructor类型的对象表示。
获取Student类中所有的属性、方法、构造方法:
Field[] fields = Student.class.getDeclaredFields(); //getFields() 字段
Method[] methods = c1.getDeclaredMethods(); //getMethods() 方法
Constructor[] cs = c1.getDeclaredConstructors(); //getConstructor()构造方法
获取Student类中公有的属性、方法、构造方法:
Field[] fields = Student.class.getFields();
Method[] methods = c1.getMethods();
Constructor[] cs = c1.getConstructors();
此外还可以获取指定的属性、方法、构造器。
4.使用反射生成并操作对象
获得了类的Class对象后,还可以通过Class对象调用newInstance()方法创建该类的对象。
Class c1 = Class.forName(“www.xszx.com”);
Student s = c1.newInstance(); //这种情况要求Student类提供了无参构造方法
创建对象的另外一种方法:通过Constructor对象。
(二六)多线程
1.继承Thread,重写run方法
通过自定义一个类(这里起名为: MyThread),继承Thread类,重写run方法,最后在main方法中 new出MyThread实例,调用这个实例的继承的Thread类的start方法创建一个线程。
1.创建出MyThread实例,并不代表在系统真的创建一个线程,只有调用start方法时,才创建出一个新 的线程,新线程会执行run里的逻辑,直到run里逻辑执行完,线程就结束了;
2.运行一次Java程序就启动了一个进程, 一个进程里至少会有一个线程,这里JVM默认创建的线程就是 main线程(主线程), main主线程和MyThread创建出来的新线程是“并发执行”的关系(并发+并
行),也可以理解为同时执行,各执行各的;
3.直接调用run并没有创建线程,只是在原来的线程中执行代码;
示例
class MyThread extends Thread {
@Override
public void run() {
System.out.println("继承Thread,重写run方法创建线程");
}
}
public class Main {
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
}
}
2.实现Runnable接口,重写run方法
通过自定义一个类(这里起名为: MyRunnable)实现Runnable接口,重写run方法,最后在main方法 new出MyRunnable实例和Thread实例,最后通过start方法创建并启动线程。
通俗理解
这里相当于把线程要干的活和线程本身分离开了,使用MyRunnable这个自定义的类来表示“线程要完成
的任务”,这样做的目的就是为了“解耦合”,假设未来有新的任务需要线程去执行,那么通过这种方式, 代码改动就比较小。
示例
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("实现Runnable接口,重写run方法");
}
public class Main {
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);
thread.start();
}
}
3.用匿名内部类创建 Thread 子类对象
直接创建Thread子类,同时实例化出一个对象,重写run方法,最后通过start方法创建并启动线 程。
示例
public class Main {
public static void main(String[] args) {
Thread thread = new Thread() {
@Override
public void run() {
System.out.println("使用匿名内部类创建 Thread 子类对象");
}
};
thread.start();
}
4.使用匿名内部类,实现Runnable接口
通过使用使用匿名内部类,实现Runnable接口作为Thread构造方法的参数,最后通过start创建 并启动线程;
示例
public class Main {
public static void main(String[] args) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("使用匿名内部类,实例Runnable接口作为构造参数");
}
});
thread.start();
}
}
5.lambda表达式
lambda本质上就是一个“匿名函数”,()表示函数的形参, {}表示函数体, ->特殊语法,表示它是 lambda表达式(->是从C++那里抄来的)。
示例
public class Main {
public static void main(String[] args) {
Thread thread = new Thread( () -> {
System.out.println("使用lambda表示创建线程");
});
thread.start();
}
}
6.实现Callable接口
通过自定义类(这里起名为: MyCallable),实现Callable接口,重写call方法(call方法可以理解为线 程需要执行的任务),并且带有返回值,这个返回表示一个计算结果,如果无法计算结果,则引发Exception异常,如下源码英文解释:
接着创建Callable实例,使用FutrueTast类包装Callable对象, FutureTask是一个包装器,需要接收 Callable实例来创建,并且有两个构造函数, 一个参数只有Callable对象,另一个参数不仅有Callable对 象,还有一个泛型的result参数,详细解释如下源码:
最后使用FutureTask对象作为Thread的构造参数,通过start方法创建并启动线程
注意:这里可以用get方法获取线程执行后的返回值。
示例
class MyCallableTest implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println("创建线程:" + Thread.currentThread().getName()); return 2;
}
}
public class Main {
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<Integer> task = new FutureTask<>(new MyCallableTest()); Thread thread = new Thread(task);
thread.start();
System.out.println("创建线程的返回结果为:" + task.get());
}
}
7.使用线程池创建线程
在Java中,线程池的本体叫ThreadPoolExecutor,他的构造方法写起来十分麻烦,为了简化构造方 法,标准库就提供了一系列工厂方法,简化使用;
什么是工厂模式?
工厂模式,将创建产品实例的权利移交工厂,我们不再通过new来创建我们所需的对象,而是通过工 厂来获取我们需要的产品。降低了产品使用者与使用者之间的耦合关系;
也就是说这里创建线程池没有显式new ,而是通过Executors这个静态方法newCaChedThreadPool来 完成的;
常见用法:
线程池的单纯使用很简单,使用submit方法,把任务提交到线程池中即可,线程池中会有线程来完成 这些任务;
示例
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Pool {
public static void main(String[] args) {
ExecutorService pool = Executors.newCachedThreadPool();
pool.submit(new Runnable() {
@Override
public void run() {
//执行业务逻辑
for(int i = 1; i <= 100; i++) {
System.out.println("线程:" + Thread.currentThread().getName() + "执行了任务" + i + "~");
}
}
});
pool.submit(new Runnable() {
@Override
public void run() {
//执行业务逻辑
for(int i = 101; i <= 200; i++) {
System.out.println("线程:" + Thread.currentThread().getName() + "执行了任务" + i + "~");
}
}
});
pool.submit(new Runnable() {
@Override
public void run() {
//执行业务逻辑
for(int i = 201; i <= 300; i++) {
System.out.println("线程:" + Thread.currentThread().getName() + "执行了任务" + i + "~");
}
}
});
//当任务量达到一定程度,一个线程忙不过来时,就会发现其他线程也来帮忙辽~
8.进程与线程
进程:每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销, 一个进程 包含1--n个线程。(进程是资源分配的最小单位)
线程:同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换开 销小。(线程是cpu调度的最小单位)
线程和进程一样分为五个阶段:创建、就绪、运行、阻塞、终止。
多进程是指操作系统能同时运行多个任务(程序)。
多线程是指在同一程序中有多个顺序流在执行。
(二八)数据库简介
关系型数据库:Mysql 、Oracle 、SqlServer.... 达梦
非关系型数据库:Redis 、MongoDB...
MySQL是一个关系型数据库管理系统,由瑞典MySQL AB 公司开发,属于 Oracle 旗下产品。MySQL 是最流行的关系型数据库管理系统之一,在 WEB 应用方面,MySQL是最好的 RDBMS (Relational Database Management System,关系数据库管理系统) 应用软件之一。
MySQL是一种关系型数据库管理系统,关系数据库将数据保存在不同的表中,而不是将所有数据放在一个大仓库内,这样就增加了速度并提高了灵活性。
MySQL所使用的 SQL 语言是用于访问数据库的最常用标准化语言。MySQL 软件采用了双授权政策,分为社区版和商业版,由于其体积小、速度快、总体拥有成本低,尤其是开放源码这一特点,一般中小型网站的开发都选择 MySQL 作为网站数据库。
结构化查询语言
SQL包括了所有对数据库的操作,主要是由4个部分组成:
1.数据定义:又称为“DDL语言”,定义数据库的逻辑结构,包括定义数据库、基本表、视图和索引4部分。
2.数据操纵:又称为“DML语言”,包括插入、删除和更新三种操作。
3.数据查询:又称为“DQL语言”,包括数据查询操作。
4.数据控制:又称为“DCL语言”,对用户访问数据的控制有基本表和视图的授权及回收。
5.事务控制:又称为“TCL语言”,包括事务的提交与回滚。
1.Mysql
1.1简单名令
连接数据库:
mysql -u 用户名 -p
查询所有数据库:
show databases;
选择使用得数据库:
use 数据库名称
创建数据库:
create database 数据库名称
删除数据库:
drop tatabase 数据库名称
查看数据库编码:
show create database 数据库名称;
查看当前数据库下面得所有表:
show tables
创建数据库表:
create table 表名称 (字段名 字段类型(长度));
查看表结构:
desc 表名称;
删除表格:
drop table 表名称;
添加数据:
insert into 表名(字段1,字段2,.....) values (值1,值2....);
注意:若表名后面什么也不写, values后面则写全字段,按顺序来,字段类型不能乱若指定添加,则在表
名称后面写清楚要添加的字段,同时values后面要和字段相对应
查询表中所有得数据:
select * from 表名;
示例
#假如是在linux环境下的,必须加封号 表示执行完了这个语句
#显示当前所有的数据库
show databases;
#选择使用数据库
use sjt2401
#创建数据库
create database syx111;
#删除数据库
drop database syx111;
#查看数据库编码
show create database sjt2401;
#查看当前数据库下面的所有表格
show tables;
#创建表格
create table syx(id int(10),name varchar(255),num int(10))
show databases;
#查看表结构
desc syx;
#删除表格
drop table syx;
SHOW TABLES;#查看当前数据库下的所有表格
#添加表格数据
create table byj(id int(10),name varchar(255),sex char(1));
insert into byj values(1,"小狗",'女');#全字段
#添加表格数据 指
insert into byj (name,sex)values("白熊",'男');#出来的结果显示id是null
show tables;#查看当前数据库下的所有表格
desc byj;#查看表格结构
#查询表格中的所有数据
#记得看左上角的数据库名称,需要匹配
INSERT INTO byj VALUES(2,"白熊",'女');#全字段
select * from byj;
#删除表格内容
#delete from byj where id=3;#指定删除
#清空表格
#delete from byj;
#修改表格结构 语法1
#ALter table byj rename bym;
#修改表格结构 语法2
#rename table bym to syx;
====================================================
#查询所有员工的信息
#Select * form emp;
#查询所有并起别名
#SELECT empno '员工编号',ename'员工姓名',job'职位',mgr'领导编号' FROM emp;
#查询公司中的职位 distinct去重关键字 select job from emp 查出来的是所有职位 但是重复
#Select distinct job from emp;
#限定查询 工资高于50的
#select * from emp where sal >=50;
#工资高于50低于500
#Select * from emp where sal>30 and sal<500;
#另一种写法 区间 左右包含
#select * from emp where sal between 50 and 500;
#查询没有奖金的员工 只能判断出0 判断不出null的
#select * from emp where comm=0
#方法二 is null 判断是否为空 用的是or
#判断是否不为空 is not null
#select * from emp where comm=0 or comm is null;
#查询有奖金的员工
#select * from emp where comm>0;
#模糊查询 查询名字中带有"马"的 包含的关键字 like
# % 是占位符 表示一个或者多个字符
# %t-->以内容开头的 t%-->以内容结尾的 %t%只要有内容的都可以 啥也没有的就相当于只查名字叫'马'的
#select * from emp where ename like'%马%';
#下划线意思是内容前面有一个字的 或者是内容后面有一个字的
#select * from emp where ename like'%_马_%'#司马懿 马前后各有一个字的
#select * from emp where ename like '%_马%'#赵马 司马懿 马前面有一个字的
select * from emp where ename like'%马_%'#三个都有 马后面至少有一个字的
1.2 SQL中的增删改查
1.增加:
insert into 表名(字段1,字段2,字段3....) values(值1,值2,值3....)
2.删除:
delete from 表名
3.修改:
update 表名 set 字段名 = 值 ,字段名 = 值 where 条件
3.1查询:
select 字段1,字段2 from 表名;
3.2限定查询
select * from 表名称 [限定条件]
限定条件:Where > , < ,>= ,<= ,between ... and ... ,and ,or, is null, in ,like
3.3去重:
select distinct 字段名称 from 表名;
4.模糊查询
select * from 表名 where 字段 like "%马%"
4.1模糊匹配:
%:占位符,表示0个或者多个字符
_: 占位符,代表一个字符
5.排序查询
select * from 表名 [限定条件] [排序条件]
order by 字段 升序/降序
升序 asc(默认)
降序 desc
6.多表查询
select * from 表1 , 表2
带来的问题:两张表做乘积处理 ,形成大量冗余数据 ,这种现象叫做笛卡尔效应;
7.左外连接查询
select 字段名 from 表1 left join 表2 on 连接条件 left join 表3 on 连接条件
- 分析过程:
- 先确定需要的表
- 确定需要的字段
- 确定关联条件
8.分组查询
- 分组条件:需要分组的列出现重复的数据
select * from 表名称 [连接查询][限定条件][分组查询][排序条件]
分组查询字段:group by
- 语法限制:
-
- 一旦出现分组,那么select后边只允许出现统计函数以及分组字段
- 统计函数可以单独使用
- 如果出现统计函数的嵌套,那么select后边只允许出现统计函数
- 但是 WHERE 和 HAVING 关键字也存在以下几点差异:
- 一般情况下, WHERE 用于过滤数据行,而 HAVING 用于过滤分组。
- WHERE 查询条件中不可以使用聚合函数,而 HAVING 查询条件中可以使用聚合函数。 3. WHERE 在数据分组前进行过滤,而 HAVING 在数据分组后进行过滤 。
- WHERE 针对数据库文件进行过滤,而 HAVING 针对查询结果进行过滤。也就是说, WHERE 根据 数据表中的字段直接进行过滤,而 HAVING 是根据前面已经查询出的字段进行过滤。
- WHERE 查询条件中不可以使用字段别名,而 HAVING 查询条件中可以使用字段别名。
9.子查询
一般而言在一个查询中,可以嵌套另一个查询,即在一个SELECT查询内在嵌入另一个SELECT查询 ,外 层的SELECT语句较外部查询,内层的SELECT语句叫子查询,子查询可以嵌套多层,但每层需要用
“ () 括起来,子查询可以用在SELECT语句中,还可以用在INSERT,UPDATE,DELETE语句中 .子查询是 一个完整的的SELECT语句,是其他SOL语句的一部分,大部分子查询是在SELECT语句的WHERE字句中 实现的,也可以放在FROM字句中当虚拟表。
Where子查询 单行单列 多行单列
Form子查询 多行多列
10.统计查询
sum求和 avg求平均值 max求最大值 min求最小值 count求个数
select count(id) from 表名
- 常见问题总结: "=="和equals的区别
“==”是运算符,如果是基本数据类型,则比较存储的值;如果是引用数据类型,则比较所指向对象 的地址值。
equals是Object的方法,比较的是所指向的对象的地址值 ,一般情况下,重写之后比较的是对象 的值。
查询员工编号在7499 7521 7566
SELECT * from emp where empno in (7499,7521,7566);
1.创建表格:
create table 表名称 (字段 字段类型(长度));
create table 表名称 (字段 字段类型(长度),字段 字段类型(长度),字段 字段类型(长度)........);
create table user (id int(3),name varchar(10));
create table person (id int(4),name varchar(10),phone varchar(11),address varchar(30));
2.查看表结构:
desc user;
desc 表名称;
3.删除表格:
drop table user;
drop table 表名称;
4.给表格中添加数据:
insert into person values (1,"admin","13988888888","taiyuan");//全字段
insert into person(id,name) values (1,“张三”)
若表名后面什么也不写,values后面则写全字段,按顺序来,字段类型不能乱
若指定添加,则在表名称后面写清楚要添加的字段,同时values后面要和字段相对应
insert into 表格名称 values (字段1,字段2....);
5.查询表格中所有数据
select * from person;
6.修改表结构:(选)
语法:(修改表字段名)
ALTER TABLE 表名称 CHANGE 原列名 想改成的列名 列类型;
ALTER TABLE test CHANGE id idd INT(10);
6.1修改表名字:
语法:1
ALTER TABLE 原表名称 rename 想改成的表名称;
alter table test rename testt;
语法:2
RENAME TABLE 原表名称 TO 想改成的表名称;
RENAME TABLE test TO testt;
修改字段类型:(一般不修改字段类型)
如varchar 修改成 int
Update 表名称 set 要修改的字段 = null //一定要赋值为null 不能是空字符串
alter table 表名称modify column 修改的字段名称 要修改成的字段类型(长度) ;
update user set name=null
alter table user modify column name int(20) ;
6.2增加字段
alter table 表名 add字段 类型(长度);
alter table user add phone int(11);
6.3删除字段
alter table 表名 drop 字段名
alter table user drop phone;
1.3 数据库中的约束
在创建表格的过程中可以给某些字段追加约束条件
1.31非空约束 NOT NULL
NK
create table t_user (
id int(3) not null,
username varchar(10),
password varchar(15)
);
1.32 唯一约束 UNIQUE
UK
create table t_user (
id int(3) not null,
username varchar(10) unique,
password varchar(15)
);
create table t_user (
id int(3) not null,
username varchar(10),
password varchar(15),
constraint UK_username unique(username)
);
1.33 主键约束 PRIMARY KEY
主键(PRIMARY KEY),也称“主键约束”。
MySQL主键约束是一个列或者多个列的组合,其值能唯一地标识表中的每一行。这样的一列或多列称为表的主键,通过它可以强制表的实体完整性。主键主要是用于其他表的外键关联,以及本记录的修改与删除。
主键(PRIMARY KEY)是 MySQL 中使用最为频繁的约束。一般情况下,为了便于 DBMS 更快的查找到表中的记录,都会在表中设置一个主键。
PK 效果上 == 非空约束+唯一约束
create table t_user (
id int(3),
username varchar(10),
password varchar(15),
constraint pk_id primary key(id)
);
联合主键
MYSQL是一个关系型数据库管理系统,其中联合主键是一种非常重要的数据库设计概念。在MYSQL中,联合主键是指使用多个列值来唯一标识一个记录的主键。一个联合主键可能包含两个或更多个字段,而这些字段一起组成了一个唯一标识符。这样可以保证在数据表中每个记录都有唯一的标识。
1.34 外键约束 FOREIGN KEY
一对一 一对多 多对多
FK 用到两张表格
t_user (一)
t_book (多)
关系维护在(多)方
uid | Username | password |
1 | Lufei | lufei |
2 | Qiaoba | qiaoba |
bid | Bookname | author | u_id(外键) |
1 | 笑傲江湖 | 金庸 | 1 |
2 | 神雕侠侣 | 金庸 | 2 |
3 | 鹿鼎记 | 金庸 | 2 |
4 | 连城诀 | 金庸 | 2 |
创建过程中分两步进行:
- 指定哪一个字段是外键 foreign key(u_id)
- 外键需要引用哪一个表格的字段 references t_user(id)
create table t_user (
id int(3),
username varchar(10),
• password varchar(15),
• constraint pk_id primary key(id)
);
create table t_book (
• bid int(3),
• bookname varchar(6),
• author varchar(3),
• u_id int(3),
• constraint pk_bid primary key(bid),
• constraint fk_u_id foreign key(u_id) references t_user(id)
);
一级菜单 二级菜单 自关联
设计一张菜单表:
id | name | url | oneid |
1 | 用户管理 | XXX | |
2 | 用户添加 | XXX | 1 |
3 | 用户删除 | XXX | 1 |
4 | 用户查询 | XXX | 1 |
5 | 用户修改 | XXX | 1 |
6 | 商品管理 | XXX | |
7 | 商品上架 | XXX | 6 |
8 | 商品下架 | XXX | 6 |
create table t_menu (
• id int(3),
• name varchar(10),
• url varchar(20),
• oneid int(3),
• constraint pk_id primary key(id),
• constraint fk_oneid foreign key(oneid) references t_menu(id)
);
外键状态下的级联操作:
级联删除谨慎使用
create table t_book (
• bid int(3),
• bookname varchar(6),
• ahthor varchar(3),
• u_id int(3),
• constraint pk_bid primary key(bid),
• constraint fk_u_id foreign key(u_id) references t_user(id) ***\*on delete cascade\****
);
create table t_book (
• bid int(3),
• bookname varchar(6),
• ahthor varchar(3),
• u_id int(3),
• constraint pk_bid primary key(bid),
• constraint fk_u_id foreign key(u_id) references t_user(id) ***\*on delete set null\****
);
1.35 检查约束 CHECK
在一个范围中间
create table test32(
id number primary key,
age number check(age>0 and age<120)
);
1.4 查询机制
emp员工表
字段 | 描述 | 类型 |
empno | 员工编号 | mediumint |
ename | 员工姓名 | varchar |
job | 职位 | varchar |
mgr | 领导编号 | mediumint |
hiredate | 入职日期 | date |
sal | 工资 | decimal |
comm | 奖金 | decimal |
deptno | 部门编号 | mediumint |
dept部门表
字段 | 描述 | 类型 |
deptno | 部门编号 | mediumint |
dname | 部门名称 | varchar |
loc | 部门位置 | varchar |
salgrade工资等级
字段 | 描述 | 类型 |
grade | 等级 | mediumint |
losal | 最低工资 | decimal |
hisal | 最高工资 | decimal |
1.41简单查询
查询所有员工信息
SELECT * from emp;
查询所有并起别名
SELECT empno '员工编号',ename '员工姓名',job '职位',mgr '领导编号' from emp;
SELECT e.empno,e.job,e.sal from emp e;
查询公司中的职位
SELECT DISTINCT job from emp;
DISTINCT :去重关键
1.42 限定查询
SELECT * FROM 表名称 [限定条件]Where > < >= <= != between...and... and or is null in like ...
查询工资高于1500的所有员工信息
SELECT * from emp WHERE sal > 1500;
查询工资高于1500低于3000的所有员工信息
SELECT * from emp WHERE sal >= 1500 and sal <= 3000;
SELECT * from emp WHERE sal BETWEEN 1500 and 3000;
查询没有奖金的所有员工信息
SELECT * from emp where comm = 0 or comm is NULL;
查询有奖金的员工信息
SELECT * from emp where comm > 0;
查询员工姓名中带有S的员工信息
SELECT * from emp where ename like '%S%';
模糊匹配:
%:占位符 代表0个或者多个字符
_:占位符 代表一个字符
1.43 排序查询
SELECT * FROM 表名称 限定条件Order by 字段 升序/降序 升序 asc降序 desc
根据工资由高到底进行排序查询
SELECT * from emp order by sal desc;
如果工资一样,那么按照入职日期升序
SELECT * from emp order by sal desc,hiredate asc;
1.44 多表查询
查询所有员工的详细信息(包含部门信息)
select * from emp,dept; (有问题的查询)
带来的问题是,简单对两张表做乘积处理,形成大量冗余数据,这种现象叫做笛卡尔积效应
需要追加两张表的关联条件,从显示上消除笛卡尔积效应
select * from emp,dept where emp.deptno = dept.deptno;
- 查询员工编号,员工姓名,员工职位,员工工资,部门名称,部门位置
先确定需要的表
emp e,dept d
确定需要的字段
e.empno,e.ename,e.job,e.sal,d.dname,d.loc
确定关联条件
e.deptno = d.deptno
select e.empno,e.ename,e.job,e.sal,d.dname,d.loc from emp e,dept d where e.deptno = d.deptno;
- 查询所有的员工编号,员工姓名,员工职位,领导姓名,领导职位
先确定需要的表
emp e1,emp e2
确定需要的字段
e1.empno 员工编号,e1.ename 员工姓名,e1.job 员工职位,e2.ename 领导姓名,e2.job 领导职位
确定关联条件
e1.mgr = e2.empno
select e1.empno 员工编号,e1.ename 员工姓名,e1.job 员工职位,e2.ename 领导姓名,e2.job 领导职位 from emp e1,emp e2 where e1.mgr = e2.empno;
发现少一条记录,如果要解决这个问题,需要借助连接查询
1.45 连接查询
SELECT * FROM 表名称 连接查询[排序条件]左(外)连接 left (outer) join...on 条件右(外)连接 right (outer) join...on 条件
- 查询所有的员工编号,员工姓名,员工职位,领导姓名,领导职位
select e1.empno 员工编号,e1.ename 员工姓名,e1.job 员工职位,e2.ename 领导姓名,e2.job 领导职位 from emp e1 left join emp e2 on e1.mgr = e2.empno;
1.46 分组查询
分组前提:需要分组的列出现了重复数据
SELECT * FROM 表名称 连接查询分组查询Group by 字段
- 语法限制:
- 一旦出现分组,那么select后边只允许出现统计函数以及分组字段
- 统计函数可以单独使用
- 如果出现统计函数的嵌套,那么select后边只允许出现统计函数
求每个部门的平均工资
select deptno,avg(sal) from emp group by deptno;
select emp.deptno,dept.dname,avg(sal) from emp,dept where emp.deptno = dept.deptno group by emp.deptno,dept.dname;
通过右连接将没有展示的数据进行展示:
select dept.deptno,dept.dname,avg(sal) from emp right join dept on emp.deptno = dept.deptno group by emp.deptno,dept.deptno,dept.dname;
展示平均工资高于2000的部门信息
错误写法:
select dept.deptno,dept.dname,avg(sal) from emp right join dept on emp.deptno = dept.deptno group by emp.deptno,dept.deptno,dept.dname where avg(sal) > 2000;
解决办法:
如果在分组之后需要再次筛选,不可以使用where,需要使用Having
select dept.deptno,dept.dname,avg(sal) from emp right join dept on emp.deptno = dept.deptno group by emp.deptno,dept.deptno,dept.dname ***\*having\**** avg(sal) > 2000;
HAVING 关键字和 WHERE 关键字都可以用来过滤数据,且 HAVING 支持 WHERE 关键字中所有的操作符和语法。
- 但是 WHERE 和 HAVING 关键字也存在以下几点差异:
1.一般情况下,WHERE 用于过滤数据行,而 HAVING 用于过滤分组。
2.WHERE 查询条件中不可以使用聚合函数,而 HAVING 查询条件中可以使用聚合函数。
3.WHERE 在数据分组前进行过滤,而 HAVING 在数据分组后进行过滤 。
4.WHERE 针对数据库文件进行过滤,而 HAVING 针对查询结果进行过滤。也就是说,WHERE 根据数据表中的字段直接进行过滤,而 HAVING 是根据前面已经查询出的字段进行过滤。
5.WHERE 查询条件中不可以使用字段别名,而 HAVING 查询条件中可以使用字段别名。
1.47 统计查询
求和、最大值、最小值、平均值 计算个数
sum max min avg count
统计一个月的工资总和
select SUM(sal) from emp;
求最高工资
select max(sal) from emp;
求最低工资
select min(sal) from emp;
求平均工资
select avg(sal) from emp;
求总条数
Select count(*/别的字段) from emp
1.48 单行函数查询
在MySQL中,单行函数是一种对单个输入值执行操作的函数。它接受一个输入参数并返回一个值。单行函数可以用于处理数据,并在查询中进行数据转换、计算、日期和时间处理等操作。
以下是MySQL中常见的单行函数示例:
- 字符串函数:
CONCAT:连接两个或多个字符串。
SUBSTRING:返回字符串的子串。
LENGTH:返回字符串的长度。
LOWER:将字符串转换为小写。
UPPER:将字符串转换为大写
- 数值函数:
ABS:返回数值的绝对值。
CEIL:返回大于或等于给定数值的最小整数。
FLOOR:返回小于或等于给定数值的最大整数。
RAND:返回一个随机数。
- 日期和时间函数:
CURDATE:返回当前日期。
CURTIME:返回当前时间。
NOW:返回当前日期和时间。
DATE_FORMAT:按照指定格式格式化日期或时间。
- 条件函数:
IF:根据条件返回不同的值。
CASE:根据条件执行不同的操作。
- 其他函数:
MD5:计算字符串的MD5哈希值。
SHA1:计算字符串的SHA1哈希值。
REVERSE:反转字符串的顺序。
TRIM:删除字符串开头和结尾的空格或指定字符。
1.49 子查询
Where子查询 单行单列 多行单列
Form子查询 多行多列
查询比SMITH工资高的人
select * from emp where sal > (select sal from emp where ename = 'SMITH');
查询比平均工资高的人
select * from emp where sal > (select avg(sal) from emp);
查询和经理工资相同的员工信息
select * from emp where sal in (select sal from emp where job = 'MANAGER');
查询部门编号,部门位置,部门人数,部门平均工资
第一步:查询每个部门的编号,名称,位置
Dept d
第二步:统计部门人数,平均工资
emp e
e.deptno deptno, count(e.empno) num,avg(e.sal) sal
Group by e.deptno
select e.deptno deptno, count(e.empno) num,avg(e.sal) sal from emp e Group by e.deptno;
将查到的数据和dept组合
select temp.deptno,temp.num,temp.sal,d.dname,d.loc from (select e.deptno deptno, count(e.empno) num,avg(e.sal) sal from emp e Group by e.deptno) temp,dept d where temp.deptno = d.deptno;
通过右连接处理边界数据
select d.deptno,IFNULL(temp.num,0) 部门人数 ,IFNULL(temp.sal,0) 平均工资 ,d.dname,d.loc from (select e.deptno deptno, count(e.empno) num,avg(e.sal) sal from emp e Group by e.deptno) temp right join dept d on temp.deptno = d.deptno;
IFNULL() 函数用于判断第一个表达式是否为 NULL,如果为 NULL 则返回第二个参数的值,如果不为 NULL 则返回第一个参数的值。
1.50 分页查询
LIMIT 关键字 参数 参数1为索引值从哪开始,第二个参数为每页显示几个
SELECT * FROM product LIMIT 30;// 从0开始,显示30条
SELECT * FROM product LIMIT 10,10;//从10开始 显示10条
由此,我们可以分析出分页查询的SQL语句格式
select * from my_book limit (page-1)*pageSize,pageSize;
其中,page是页码,pageSize是每页显示的条
当数据库中数据量较大时,使用limit函数会存在性能问题,我们可以用主键来代替limit函数实现数据分页查询
select * from my_book where id > (page-1)*pageSize limit pageSize;
select * from my_book where id > 10 limit 5;
2.事务处理
概念:在一个业务逻辑中包含多个步骤操作,需要被事务进行管理,要么同时成功,要么同时失败
A -500
//异常
B+500
默认提交事务
关闭默认提交事务:set autocommit = 0;
手动提交:commit
手动回滚:rollback
开启默认提交事务:set autocommit = 1;
2.1 事务的四大特性
1、原子性:事务包含的所有数据库操作要么全部成功,要不全部失败回滚
2、一致性:一个事务执行之前和执行之后都必须处于一致性状态。拿转账来说,假设用户A和用户B两者的钱加起来一共是5000,那么不管A和B之间如何转账,转几次账,事务结束后两个用户的钱相加起来应该还得是5000,这就是事务的一致性。
3、隔离性:一个事务未提交的业务结果是否对于其它事务可见。级别一般有:read_uncommit,read_commit,read_repeatable,串行化 访问。
4、持久性:一个事务一旦被提交了,那么对数据库中数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作。(持久化操作)
2.2 事务的隔离级别
Read uncommitted 读未提交
产生问题:脏读、不可重复读、幻读
提供了事务建最小限度的隔离。顾名思义,就是一个事务可以读取另一个未提交事务的数据。
- 示例:小明去商店买衣服,付款的时候,小明正常付款,钱已经打到商店老板账户,但是小明发起的事务还没有提交。就在这时,商店老板查看自己账户,发现钱已到账,于是小明正常离开。小明在走出商店后,马上回滚差点提交的事务,撤销了本次交易曹邹。
- 结果:小明未付钱买到了衣服,商店老板实际未收到小明的付款。
- 分析:商店老板查看自己的资金账户,这个时候看到的是小明还没有提交事务的付款。这就是脏读。
- 注意:处于该隔离级别的事务A与B,如果事务A使用事务B不提交的变化作为计算的基础,然后哪些未提交的变化被事务A撤销,这就导致了大量的数据错误变化。
Read committed 读已提交(Oracle数据库默认的隔离级别)
产生问题:不可重复读、幻读
处于Read committed (读已提交)级别的事务可以看到其他事务对数据的修改。也就是说,在事务处理期间,如果其他事务修改了相应的表,那么同一个事务的同一sql在其他事务执行前后返回的是不同的结果。一个事务要等另一个事务提交后才能读取数据。
- 示例:小明卡里有1000元,准备与几个朋友聚餐消费,消费1000元,当他买单时(事务开启),收费系统检测到他卡里有1000元。就在检测完dog的时候,小明女朋友发现小明有私房钱,全部转走并提交。当收费系统准备扣款时,再检查小明卡里的金额,发现已经没钱了,付款不成功。小明此时就会很纳闷,明明有钱的呀,钱呢?
- 分析:该示例中同一个事务范围内两个相同的查询却返回了不同数据,这就是不可重复读。该隔离级别可以解决脏读问题。
Repeatable read 可重复读(Mysql数据库默认的隔离级别)
产生问题:幻读
在开始读取数据(事务开启)时,不再允许修改操作
- 示例:还是小明有1000元,准备跟朋友聚餐消费这个场景,当他买单(事务开启)时,收费系统检测到他卡里有1000元,这个时候,他的女朋友不能转出金额。接下来,收费系统就可以扣款成功了,小明醉醺醺的回家,准备跪脱衣板。
- 分析:重复读可以解决不可重复读的问题,这句话有些别扭,大家可以仔细品一下。
写到这里,大家可能会产生疑问,什么情况下产生幻读呢?
- 示例来了:
小明在公司上班,女朋友告诉他,拿着他的卡去逛街消费。花了一千元,然后小明去查看他银行卡的消费记录(事务开启),看到确实是花了一千元。就在这个时候,小明女朋友又花三千元买了一些化妆品和衣服,即新增了一些消费记录。当小明打印自己银行卡消费记录单的时候(女朋友事务提交),发现花了四千元,似乎出现了幻觉,小明很心疼。这就是幻读
Serializable 串行化
解决所有问题
隔离级别越高,性能越低
查询数据库的默认隔离级别:
select @@tx_isolation;
改变数据库的隔离级别:
set global transaction isolation level 隔离级别关键字;
//设置read uncommitted级别:
set session transaction isolation level read uncommitted;
//设置read committed级别:
set session transaction isolation level read committed;
//设置repeatable read级别:
set session transaction isolation level repeatable read;
//设置serializable级别:
set session transaction isolation level serializable;
开启事务:
Start transaction
Update person set sal = sal - 500 where id = 1;
Update person set sal = sal + 500 where id = 2
3.JDBC基础操作
3.1 步骤
第一步:下载驱动
第二部:导入驱动
第三部:加载驱动
第四部:驱动管理器运行,获取链接Connection
第五步:通过链接Connection创建数据库操作对象Statement
第六步:通过数据库操作对象Statement执行sql
第七步:关闭链接
Class.forName("com.mysql.jdbc.Driver");
String url ="jdbc:mysql://localhost:3306/test";
String user = "root"\;
String password = "root";
Connection conn= DriverManager.getConnection**(url,user,password);
Statement statement =conn.createStatement();
statement.execute(sql);
conn.close();
没有导入驱动会报错
execute返回boolean类型,true表示执行的是查询语句,false表示执行的是insert,delete,update等等executeUpdate返回的是int,表示有多少条数据受到了影响
3.2 DriverManager 驱动管理对象
用于管理一组JDBC驱动程序的基本服务,(比如通过它获取连接对象)。
Class.forName(“com.mysql.jdbc.Driver”);
Class.forName(“全类名”)是将某个指定的类加载到内存中,所以是将"com.mysql.jdbc.Driver"类加载进内存。该类在mysql-connector-java-5.1.37-bin.jar库里面。
当类加载进内存时,会自动执行类中的静态代码块。
当 Driver 类加载进内存时会执行类中的静态代码块,即创建一个 Driver 类对象(即驱动程序对象),并调用 DriverManager.registerDriver 方法注册创建的驱动程序。注册的目的是让驱动程序管理器知道所创建的驱动程序对象,以便管理。如果驱动程序当前已注册,则不采取任何操作。
3.3 Connection 数据库连接对象
与给定的数据库 URL 建立连接(会话)。可以在会话期间执行 SQL 语句并返回结果,并且能够管理事务。
1)获取方式
Connection conn = DriverManager.getConnection(“jdbc:mysql://localhost:3306/db3”, “root”, “root”);
getConnection方法参数:
url:指定连接的路径(不同数据库写法不一样)
语法:jdbc:mysql://ip地址(域名) : 端口号/数据库名称
例如:jdbc:mysql://localhost:3306/db3
细节:如果连接的是本机mysql服务器,并且mysql服务默认端口是3306,则url可以简写为:jdbc:mysql:///数据库名称
user:用户名
password:密码
即通过驱动管理对象 DriverManager 调用 getConnection 方法从已注册的 JDBC 驱动程序集中选择(连接)一个合适的驱动程序,返回一个数据库连接对象。
2)管理事务:
事务概念:一个包含多个步骤的业务操作。如果这个业务操作被事务管理,则这多个步骤要么同时成功,要么同时失败。
使用 Connection 对象来管理事务
开启事务:setAutoCommit(boolean autoCommit) :调用该方法设置参数为false,即开启事务。
在执行sql之前开启事务
提交事务:commit()
当所有sql都执行完提交事务
3)回滚事务:rollback()
在catch中回滚事务
3.4 Statement:执行sql语句的对象
1)获取方式
Statement stmt = conn.createStatement();
创建一个语句对象,用于向数据库发送 SQL 语句。通过连接对象 Connection 的 createStatement() 方法创建用于处理 SQL 语句的 Statement 对象。
调用不同的方法处理相应的 sql 语句
boolean execute(String sql) :可以执行任意的sql (了解)
int executeUpdate(String sql) :执行DML(insert、update、delete)语句、DDL(create,alter、drop)语句。返回值:影响的行数,可以通过这个影响的行数判断DML语句是否执行成功返回值>0的则执行成功,反之,则失败。
ResultSet executeQuery(String sql) :执行DQL(select)语句。
ResultSet:结果集对象,封装查询结果。比较重要的方法。
boolean next(): 游标向下移动一行,判断当前行是否是最后一行末尾(是否有数据),如果是,则返回false,如果不是则返回true。
getXxx(参数):获取数据。
Xxx:代表数据类型 如: int getInt(), String getString()
参数:int:代表列的编号,从1开始 如:getString(1); String:代表列名称。 如: getDouble(“balance”)
使用步骤:
1.游标向下移动一行
2.判断是否有数据
3.获取数据
3.5 PreparedStatement 执行sql的对象
- SQL 注入问题:Statement 对象执行 sql 存在的问题,在拼接 sql 时,有一些 sql 的特殊关键字参与字符串的拼接。会造成安全性问题。(简单的字符串拼接)
- 解决 sql 注入问题:使用 PreparedStatement 对象来解决。PreparedStatement 防止 SQL 注入的本质是将传递进来的参数当做不含其他语义的一个字符串,如果其中存在非法的转义字符(‘),会被直接转义。最终传入参数作为一个整体执行,这样能够避免字符串拼接成非法的 sql 语句。
- 预编译的 SQL:参数使用 ? 作为占位符。
4· ResultSet 接口。保存查询结果。
课后练习
package day18.homework.util;
import java.sql.*;
/**
* @Author: 白熊
* @Date: 2024/3/10 16:52
* JDBC工具包
*/
public class JDBCutil {
static String url = "jdbc:mysql://localhost:3306/sjt2401";
static String user = "root";
static String password = "123456";
//加载驱动
static {
try {
Class.forName("com.mysql.cj.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
public static Connection getConnection(){
Connection coon = null;
try {
//通过账号密码连接
coon = DriverManager.getConnection(url,user,password);
} catch (SQLException e) {
e.printStackTrace();
}
//返回一个coon对象
return coon;
}
public static void close(Connection coon, PreparedStatement ps, ResultSet rs){
//关流时要传入对象
try {
if(rs!=null) {
rs.close();
}
if(ps!=null) {
ps.close();
}if(coon!=null) {
coon.close();
}
}catch (SQLException e) {
e.printStackTrace();
}
}
}