Unit_1 -- Java 应用程序开发
# Part_2
First : Java 运算符
1. 运算符和表达式
1.1 运算符基本分为六类:算数运算符;赋值运算符;比较/关系运算符;逻辑运算符;位运算符;三元/三目/条件运算符;
1.2 运算符和表达式是 Java程序的基本元素。运算符是一种特殊的符号,用以表达数据的运算、赋值 和比较,不通的运算符用来完成不通的运算;
1.3 Java语言使用运算符将一个 或 多个操作数连缀成执行的语句,形成表达式;表达式是由运算符和操作数按照一定的语法规则组成的符号序列。
//Java 合法表达式
a+b;
(a+b)*(a-b);
"name=" + "张三"
//表达式经过运算符后都会产生一个确定的值,一个常量或一个变量是最简单的表达式
2. 算数运算符
算数运算符被用在数学表达式中,可以使用任意嵌套的小括号,其作用与数学中相同!
分类:初始化两个整型变量 a , b : int a = 5; int b = 2;
NO | 分类 | 例子 |
---|---|---|
1 | 加法运算符(+) --- 也用于字符串拼接 | a + b = 7 |
2 | 减法运算符(-) | a - b = 3 |
3 | 乘法运算符(*) | a * b = 10 |
4 | 除法运算符(/) | a / b = 2 |
5 | 取余运算符(%) | a % b = 1 (取的是 a/b 的余数) |
/*
* 案例
*/
public class operator{
public static void main(String[] args){
int a = 5; //定义两个变量
int b = 2;
int result1 = a + b; //7
int result2 = a * b; //10
int result3 = a / b; //2
int result4 = a - b; //3
int result5 = a % b; //1
}
}
注意事项:浮点数值 和 整数数值之间进行 加/减/乘/除/取余, 得出的结果仍然是浮点数值;
字符串类型使用 “+” 时是拼接字符串;
3. 逻辑运算符
逻辑运算符可以在表达式中生成组合条件;例如:在执行特定语句块之间必须满足两个或多个条件,逻辑的返回结果只能是布尔值(真 或 假);
分类:初始化两个整型变量 a , b ; int a=5; int b = 2;
No.1 >> &&(逻辑与) ---> 当且仅当两个操作数都为真,条件才为真;
No.2 >> ||(逻辑或)---> 如果任何两个操作数 其中一个为真,那么条件就为真;
No.3 >> ! (逻辑非) --->用来反转操作数的逻辑状态;如果条件为真,则逻辑非运算符将得到假;
4. 关系运算符
关系运算符又称之为比较运算符;比较的结果是一个布尔类型的值(true / false)
No.1 : "==" : 检查两个操作数的值是否相等,如果相等则条件为真;
No.2 : "!=" : 检查两个操作数的值是否相等,如果值不相等则条件为真;
No.3 : ">" :检查左操作数是否大于右操作数,如果是,那么条件为真;
No.4 : "<" :检查左操作数是否小于右操作数,如果是,那么条件为真;
No.5 : ">=" :检查左操作数是否大于 或 等于右操作数,如果是,那么条件为真;
No.6 : "<=" :检查左操作数是否小于 或 等于右操作数,如果是,那么条件为真;
>、<、>=、<= 这几个运算符两边的操作数必须是 byte / short / int / long / double / float / char 这几种数据类型;而 "==" 、"!=" 运算符的操作数既可以是基本数据类型,又可以是引用数据类型
5. 自增自减运算符
在运算结束前(前置自增自减运算符) 或后(后置自增自减运算符)将变量的值加(减)一;
//分类:初始化两个整型变量:a, b : int a = 2; int b = 4;
//自增 “++”
先使用 a 的值,然后再让值 +1
a++; //2
先 +1 ,再去使用a 的值
++a; //3
======================分==割==线===================================
Second : 流程控制语句
01:条件语句
if 条件:
语法:
if(条件){ //
//代码块
}
public static void main(String[] args){
int a = 10;
int b = 5;
if(a>b){ // 条件的结果为布尔值
System.out.println("right");
}
}
if...else...条件:
语法:
if(条件){
//代码块_1
} else{
// 代码块_2
}
public static void main(String[] args){
int a = 10;
int b = 5;
if(a>b){ // 条件的结果为布尔值
System.out.println("right");
} else{
System.out.println("Wrong!");
}
}
if...else if...else条件:
语法:
if(条件1){
//代码块_1
}else if(条件2){
//代码块_2
}else{
//代码块_3
}
public static void main(String[] args){
int score = 81;
if(score>=80){
System.out.println("优秀");
}else if(score>=60){
System.out.println("良好");
}else{
System.out.println("不及格");
}
}
switch语句:
语法:
switch(表达式){
case 常量1:
语句;
break;
case 常量2:
语句;
break;
......
default:
语句:
break;
}
public static void main(String[] args){
int i = 2;
switch(i){
case 1:
System.out.println("i的值为1")
break;
case 2:
System.out.println("i的值为2")
break;
defult:
System.out.println("i的值既不等于1,也不等于2")
}
}
>>switch 语句中的表达式类型可以是:byte 、short 、int 、 char ,JDK7版本以后可以使用String;
>>case 语句不一定要包含 break语句,如果没有break语句出现,程序会继续执行下一个case语句,直到出现break语句
02:for循环语句、while循环语句、do...while..循环语句
for循环语句:
语法:
for(int i=0;i<10;i++){
//代码块 ---循环体
}
>> int i=0; ①初始化循环变量
>> i<10; ②循环条件
>> i++; ④操作循环变量
>>//代码块 ; ③循环体
for 循环的执行步骤按语法步骤 ①--②--③--④顺序执行,相当于第一次循环;第二次循环开始直接从步骤②开始执行,知道循环执行完毕
//for循环对 2 取余
public class Control{
public static void main(String[] args){
for(int i=0;i<100;i++){
if( i%2 ==0 ){
continue;
//break;
}
System.out.println(i);
}
}
}
while循环语句
语法:
while(循环条件){ ---符合调价会继续执行
//循环操作 --- 循环会重复操作
}
//循环累加 1 到 100 累加的和
public static void main(String[] args){
int sum = 0;
int i = 1;
while(i <= 100){
sum = sum + i;
i++;
}
System.out.println("总和为:" + sum);
}
do...while..循环语句
语法:
do{ --- 先执行一次循环操作
//循环操作
} while(循环条件);
--- 符合while条件,循环则会继续执行,否则循环退出
// 1-->100累加的和
public static void main(String[] args){
int sum = 0;
int i = 1;
do{
sum = sum + i;
}while(i <= 100);
System.out.println("和为:" + sum);
}
03:continue 和 break 的区别
功能:break 与 continue 语句都hi是程序能够跳过部分代码
区别:break : 结束所有循环,本次循环不再执行,跨出循环体以内的内容;
continue:结束本轮循环,开启下一轮循环,本轮循环剩下的内容不再执行;
注意:break 可以在 switch 语句中使用,而continue 不能
======================分==割==线===================================
Third : 数组
01:定义数组的三种方式;
1.1:数组:
数组是相同类型的数据 按照顺序组成的一种 引用数据类型,数组分为 一维数组 和二维数组;
1.2:创建数组的三种方式:
>>>:数组类型[] 数组名 = new 数组类型[数组长度];
//
int[] nums = new int[5];
>>>:数组类型[] 数组名 = new 数组类型[]{元素1,元素2,......};
//
int[] nums = new int[]{1,2,3,4,5,6};
>>>:数组类型[] 数组名 = {元素1,元素2,......};
//常用的方式
int[] nums = {1,2,3,4,5,6};
1.3:声明数组的两种方式:
》》:先声明,再创建
//先声明,再创建
DataType[] arrayName;
arrayName = new DataType[5];
》》:声明时同时创建
//声明同时创建数组
DataType[] arrayName = new DataType[10];
注意事项:
· 中括号跟在数组类型后:DataType[] arrayName;
· 中括号跟在数组名称后:DataType arrayName[];
1.4:数组的初始化:
静态初始化 和 动态初始化
静态初始化:
int[] arr = {1,2,3,4,5}
上面的数组 arr 存放的元素为从 1 到 5 这几个整型,长度为 5;数组的长度就是初始化时所给的数组元素的个数;
动态初始化:
int[] arr = new int[3];
arr[0] = 1;
arr[1] = 2;
arr[2] = 3;
数组的声明以及创建是与数组的赋值操作分开进行的;
数组都是有下标的,下标从 0 开始,因此长度为 3 ,最大下标为 2;
数组在赋值之前,其默认值都为 0 ;
02:数组的遍历
2.1:for 循环遍历数组
// 定义一个数组,使用循环进行赋值
//for 循环进行赋值
public static void main(String[] args){
//声明一个整型数组
int[] arr = new int[10];
//使用for循环对数组 arr 进行遍历,将数组 1--10 赋值给数组arr
for(int i=0;i<arr.length;i++){
arr[i] = i + 1;
}
//赋值完成,此时 arr = {1,2,3,4,5,6,7,8,9,10}
//遍历数组,打印数组中的每一个元素
for(int i=0;i<arr.length;i++){
System.out.println(arr[i] + "\t");
}
}
2.2:foreach 循环遍历数组
增强for循环 --- foreach 遍历
语法:
for(变量声明:数组或列表){
// 循环体
}
//增强 for 循环 foreach
public static void main(String[] args){
String[] words = {"Welcome","to","China"};
for(String world:words){
System.out.println(world);
}
}
注意:增强 for 循环无法指定遍历数组顺序,也无法获取数组的索引,实际开发中比较常用于遍历;
03:使用冒泡排序
方式一:
Arrays.sort();
在Java中最简单且最常用的方法;
方式二:
冒泡排序;
冒泡排序就是重复的走访过要排序的数组,依次比较两个元素,如果他们的顺序错误 就把他们交换过来!
//冒泡排序
public static void main(String[] args){
int[] arr = {90,70,50,80,60,85};
//第一个循环控制 n-1 趟顺序
for(int i=0;i<arr.length-1;i++){
//第二个循环控制每次要比较的元素个数 n-1
for(int j=0;j<arr.length-i-1;j++){
if(arr[j] > arr[j+1]){
int flag = arr[j];
arr[j] = arr[j+1];
arr[j+1] = flag;
}
}
}
}
方式三:
发转排序:
将原数组按逆序排列
int[] arr = {23,12,48,56,45};
for(int i=0;i<arr.length;i++){
int tp = arr[i];
arr[i] = arr[arr.length-i-1];
arr[arr.length-i-1] = tp;
}
Fourth : 方法
01:方法的定义
1.1:定义:
方法的本意就是功能块,就是实现某个功能的语句块的集合。
平时设计方法的时候最好保持方法的原子性,就是一个方法只完成一个功能,这样有益于后期的扩展,我们平时使用的输出语句 println() 就是Java的一个方法
方法在程序中被创建,在其他地方被引用;(例如:System.out.println() 在编译过程中,调用带括号的内容都是方法)
1.2:方法的组成部分
》》 访问修饰符 : public ; protected;默认的; private ;这是可以选择的;
》》 返回值类型:方法可能会有返回值,也可能没有返回值,返回值的数据类型可以是基本数据类型,也可以是引用数据类型;
》》 方法名: 方法的实际名称,根据实际情况可以按照标识符规则定义名称,做到见名知义;
》》 参数:参数是变量的一种类型,参数变量的作用域在方法内部;
》》 方法体:方法内部的一些语句。当方法返回值为 void 时,可以省略 return语句;
1.3: 语法
访问修饰符 返回值类型 方法名(参数列表){
若干语句;
return 方法返回值类型;
}
//方法的语法
public void println(String x){
if(getClass() == PrintSteam.class){
writeln(String.valueOf(x));
}else {
sychronized(this){
print(x);
newLine();
}
}
}
02:方法的创建
------ 创建方法的 4 种方式:
2.1:无参无返回值
public [static] void getNum(){}
public void getNum(){
System.out.println("无参无返回值的方法");
}
2.2:无参有返回值
public [static] int getNum(){
return 值;
}
public int getNum(){
int num = 20;
return 30; //返回值必须和返回值类型保持一致
}
2.3:有参无返回值
public [static] void getNum(int number){ }
//可以有多个参数,类型也可以不一致,可以是基本数据类型,也可以是引用类型
public void getNum(int n1, int n2, String str){
System.out.println("有参无返回值方法");
}
2.4:有参有返回值
public [static] int getNum(int n1, int n2){
return 值;
}
// static 静态方法修饰,静态调用
//int num, int[] nums 形式参数
//形参就是在方法被调用时,用于接收外界输入的数据,也就是定义方法时的参数
public static int getSum(int num, int[] nums){
int sum = 0;
for(int n:nums){
sum = num*n;
}
return sum; //返回sum的值
}
03:调用静态方法和普通方法
3.1:方法调用
》》 方法和方法之间可以相互调用,类似 println() 方法可以在 main 方法中调用
案例:使用 main 方法调用 getSum 方法;
//(2, nums1) 为实际参数;实际参数就是方法被调用的时候传递进来的实际值
public static void main(String[] args){
int[] nums1 = {3, 2};
int sum1 = getSum(2, nums1);
System.out.prinln("获取总数为:" + sum1);
}
》》》注意:静态方法能够直接调用静态方法,但不能直接调用非静态方法
》》》上面的 getSum 方法如果不加 static ,main 方法调用会编译出错,可以通过创建 Test 类对象使用对象方式进行调用
//
public class Test{
static int num;
//main 方法
public static void main(String[] args){
System.out.println(num); //0
Test test = new Test(); //静态方法调用非静态方法,需要创建对象
getNum1();
test.getNum2();
}
//getNum1 方法
public static void getNum1(){
num = 10;
System.out.println(num);
}
//getNum2 方法
public void getNum2(){
getNum1(); //非静态方法可以直接调用非静态方法和变量
num = 20;
System.out.println(num);
}
}
3.2: 静态方法
------ 静态方法不能直接调用非静态方法和变量;
静态方法:当类加载静态方法区时,静态方法就已经在内存里有了自己的实际空间,因此静态方法无需实例化,使用 static 修饰;
》》以上 ,num 是静态变量,getNum() 是静态方法,可以在main 方法中直接调用,但是不能直接调用 getNum2() ,只能通过创建对象实例才能去调用
3.3:静态方法和非静态方法的区别
04:方法参数和返回值类型
Fifth:String 类型
01:String 类的创建方式
1.1:String 类
字符串广泛应用在 Java 编程中,在Java中 String 类型是引用数据类型;
1.2:String 类的特点
》》》String 类是最终(final) 类,是引用数据类型,不能被继承;
》》》String 定义的内容一旦声明,原则上不可改变;
》》》String 类对象的相等使用 equals() 方法来完成,“==” 实现的是地址数值的比较。
1.3:String 类的创建方式
1.3.1:直接方式创建:
String name = "张三";
特点:引号所创建的字符串即直接赋值在字符串池中;
1.3.2:创建对象的方式创建
String name = new String(“张三”);
注意事项:new 创建字符串时首先查看池中是否有相同的字符串,如果有,则拷贝一份在堆中,然后返回堆中的地址;如果池中没有,则在堆中创建一份,然后返回堆中的地址;
02:String 的存储原理
demo:
//创建 StringTest 类
String str1 = "hello";
String str2 = "hello";
System.out.println(str1 == str2); //结果为 true
String x = new String("xyz");
String y = new String("xyz");
System.out.println(x == y); //结果为 false
>>> str1 和 str2 的值存储在常量池中存的东西都是字符串 “hello” 字符串常量中的内存地址;
>>> 尽管两个 String 对象中存储的字符串常量池的内存地址相同,都执行字符串 “xyz” ,但是 main 方法栈里的两个变量 x,y 存储的是这两个不通的String 对象的堆的内存地址,所以一比较,还是不相等的;
>>>其中 “==” 比较的是内存地址;
03:String 常用方法
04:String 和 StringBuffer ,StringBuilder 的区别
/* 字符串反转的两种方法*/
public class testdemo{
public static void main(String[] args){
String str2 = "Hello";
str2 = new StringBuffer(str2).reverse().toString();
System.out.println(str2);
String message = "Hello";
StringBuilder rev = new StringBuilder();
for(int i = message.length-1;i>=0;i--){
rev.append(message.charAt(i));
}
System.out.println(rev.toString);
}
}
Sixth :面向对象编程
--
01:面向对象的三大特征
概念:对象是对现实实物的模拟。可以把万物万事都看作各种对象,将具有共同的属性和行为抽象出来,使用数据和方法来描述对象的状态和行为;
//类
public class ChineseTeacher{
//共有的属性
private String name;
private String sex;
private int age;
// 方法 -- 共有的行为
public void teach(){
}
}
1.1:封装
对类中的属性进行隐藏,对外只提供 set 和 get 方法,防止外类对类的属性随意修改,提高安全性;
修改属性设为 private --->> 创建属性的 setter / getter 方法 --->>在setter / getter 方法中加入控制语句;
// Person 类
public class Person{
private String name; //使用 private 创建私有属性;外部类不可用
private int age;
private String sex;
public String getName(){ //对外提供 get / set 方法
return name;
}
public void setName(String name){
this.name = name;
}
public int getAge(){
return age;
}
public void setAge(int age){
//判断age的合法性
if(age < 0){
this.age = 0;
}
this.age = age;
}
}
1.2:继承
通过 extends 关键字,可以声明多个子类继承一个父类,子类中无需再去定义共同的属性和方法,只需要在父类中定义即可,这样子类可以继承到父类中的属性和方法;
》》》子类只能继承一个父类,子类可以派生出多个子类;final定义的类
》》》不能被继承,final 和 static 修饰的方法和属性不能被继承
//定义一个 【 父类 Pet 】
public class Pet{
public void shout(){
System.out.println("宠物叫");
}
}
// 创建一个 【子类 Dog】
public class Dog extends Pet{
@Override //重写父类方法
public void shout(){
System.out.println("狗叫");
}
public void sleep(){
System.out.println("狗睡觉");
}
}
// 创建子类 【子类 Cat】
public class Cat extends Pet{
@Override
public void shout(){
System.out.println("猫叫");
}
public void eat(){
System.out.println("猫吃饭");
}
}
1.3:多态
概念:
同一操作可以作用于不同的对象,可以有不通的解释,产生不同的执行结果,这就是多态特征;
多态性发生在当父类引用 指向 子类对象时。
------>在面向对象中,所谓多态意指相同的消息给予不同的对象会引起不同的动作;简而言之:多态意味着允许不同类的对象对同一消息做出的不同响应
实现多态的三个条件
存在的三个必要条件:
①:要有继承关系的存在;
②:要有方法的重写;
③:要有父类引用指向子类对象。
举例:
Dog 类 和 Cat 类都继承自 Pet 类,这些类下都有各自的 shout() 方法,Pet 类中的 shout() 方法输出“宠物叫” ,而 Dog类中的 shout() 方法输出“狗叫” ,Cat 类中的 shout() 方法则输出“猫叫” ,Cat 和 Dog 类都继承于父类的 shout() 方法,但是不同的对象拥有不同的操作
02:访问修饰符的作用范围
2.1:访问修饰符
Java 的四个修饰符分别为 : public ; protected ; friendly(默认的) ; private ; 可以用来修饰类、方法、变量;
2.2: 作用范围
修饰符 | 类内部 | 同一个包 | 不同包子类 | 同一个project |
---|---|---|---|---|
public | Y | Y | Y | Y |
protected | Y | Y | Y | |
默认的 | Y | Y | ||
private | Y |
03:包的定义和导入
3.1:定义:
格式: package 包名
实例: package com.alibaba.entity
------ 定义包的名称使用小写
3.2:导入包关键字
格式:import 包名
实例: import java.util.ArrayList
04:this 和 super 的区别
区分点 | this | super | |
---|---|---|---|
1 | 访问属性 | 访问本类中的属性,如果本类没有此属性,则从父类中查找 | 访问父类中的属性 |
2 | 调用方法 | 访问本类中的方法 | 直接访问父类中的方法 |
3 | 调用构造器 | 调用本类构造器,必须放在构造器的首行 | 调用父类构造器,必须放在子类构造器的首行 |
4 | 特殊点 | 表示当前对象 | ———— |
05:重写和重载的区别
区分点 | 重写 | 重载 | |
---|---|---|---|
1 | 定义 | 子类重写父类方法 | 定义方法名相同,参数不同 |
2 | 范围 | 子类和父类之间 | 在同一个类中 |
3 | 多态 | 运行时的多态性 | 编译时的多态性 |
4 | 参数 | 子类重写的方法参数必须和父类相同 | 参数的个数、类型、顺序可以不同 |
5 | 修饰 | 重写方法的修饰范围大于被重写方法的修饰范围 | 对修饰范围没有要求 |
06:匿名内部类的创建方式
定义:匿名内部类是不能有名字的类,它们不能被引用,只能在创建时用 new 语句来声明他们;
格式:
// 匿名内部类的创建方式
// 格式
class outerClass{
//定义一个匿名类
object1 = new Type(parameterList){
//匿名内部类代码
};
}
07:成员变量和局部变量的区别
Seventh:抽象类和接口
01:抽象类的实现
1.1:抽象类定义
在面向对象的概念中,所有的对象都是通过类来描述的,如果一个类中没有包含足够的信息来描述一个具体的对象,这样的类就叫做抽象类
1.2:抽象类特点:
》》抽象类不能直接实例化,但类的其他功能依然存在,它只有被继承才可以使用;
》》当某个父类只知道其子类应该包含什么方法,但是不知道子类如何实现这些方法的时候,抽象类就派上用场了。但抽象类还有一个好处:对于使用者来说,有一个提示的作用;
1.3:抽象类的创建方式
// 声明抽象类 abstract关键字必须放在 class 前面
public abstract class Pet{
......
}
🎗 抽象类中可以定义与普通方法一样的属性、方法等内容,和普通类不一样的地方在于抽象类中可以定义抽象方法,另外,抽象类不能被实例化对象(不能用它创建对象)
1.4:抽象类中的抽象方法
抽象方法:它是没有具体的实现方法。换句话说:与普通方法不同的是,抽象方法没有用 { } 包含的方法体,抽象类可以只声明方法,而不关系这些方法的具体实现;
抽象类注意事项:抽象方法不能使用 private、final、以及 static 关键字修饰,因此私有方法、最终方法以及静态方法都不可以被子类重写
举例:
abstract class Pet{
abstract void shout(); //--抽象方法
void eat(){ //--eat方法非抽象
//方法体可省略
}
}
class Dog extends Pet{
//子类必须要实现父类 shout() 方法
// eat 方法可以选择使用
}
02:接口的实现
2.1:接口的定义
Java 接口时一系列方法的声明,是一些方法特征的集合,一个接口没有方法的实现。被关键字 interface 修饰的类就是一个接口,接口中定义了一组抽象方法,都没有具体实现,实现该接口的类就必须实现该接口中定义的所有抽象方法;
2.2:接口特点
2.2.1:达到封装的目的,使调用方不用感知内部实现的逻辑
2.2.2:接口比抽象类更加“抽象”,不能拥有具体实现的方法,必须全部都是抽象方法,所有的方法默认都是 public abstract 的,所以在接口主体中的方法,这两个修饰符无锡显示指定;
2.2.3:接口除了方法声明外,还可以包含常量的声明。在接口中定义的所有的常量默认都是 public 、static 和 final 的
2.3:使用接口的意义
Java仅支持单继承,也就是说一个类只允许一个直接的父类,Java不支持多继承,但是可以实现多个接口,没有继承关系的类也可以实现相同的接口
2.4:接口的创建方式
implements 关键字用于实现接口,一个类可以实现一个或多个接口
当需要实现多个接口时 implements 关键字后面是该类要实现的以 “,” 作为分割接口名列表
// 声明接口
public interface Mydemo{
//interface 用于定义接口的关键字
}
//实现接口
public class Myclass implements Mydemo1,Mydemo2{
......
}
03:抽象类和接口的区别
Eighth :集合类型 set 和 List
01:List 和 Set 集合的使用以及常用方法
1.1:List 集合
定义:List 接口时容器框架中 继承了 Collection 接口,声明 有序存储对象(可重复) 功能的公共接口;
List 接口的实现类:ArrayList 、Vector 、 LinkedList
注意事项:①:ArrayList 保证数据有序;②:ArrayList 数据是可以重复的;③:ArrayList 数据结构是数组;
1.2:List 集合常用方法
//ArrayList 集合举例说明
List<String> lists = new ArrayList<String>();
lists.add("张三");
lists.add("李四");
lists.add("王五");
//循环 for 方式
for(int i=0;i<lists.size();i++){
System.out.println("下标查询" + lists.get(i));
}
//循环 foreach 方式
for(String s:lists){
System.out.println(s);
}
//修改
lists.set(2,"赵六");
//根据下标 删除
lists.remove(2);
//根据元素 删除
lists.remove("李四")
System.out.println(lists);
1.3:Set 集合
定义:Set 接口时容器框架中继承了 Collection接口,数据是不能重复的,最多可以存储一个 null 值;
Set 接口的实现类:HashSet、 LinkedHashSet、和 TreeSet;
注意事项:①:HashSet 不能保证数据有序;②:HashSet 数据是不能重复的;③:HashSet 是可以存储 null 值;
1.4:Set 集合常用方法
// HashSet 集合举例
Set<Integer> set = new HashSet<Integer>();
set.add(10);
set.add(12);
set.add(11);
set.add(14);
set.add(10);
// 循环遍历,迭代器方式
Iterator<Integer> iterator = set.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
1.5:Set 接口 和 List 接口 的区别
02:ArrayList 集合的使用和运行原理
2.1:ArrayList 定义
基于数组实现的,长度可变的,分配连续内存空间的 List 集合;
2.2:实现步骤
03:Collection 实现排序
3.1:Collections
Collections 是集合工具类,用来对集合进行操作,区分于Collection,
排序方法: public static <T> void sort(List<T> list)
04:Collection 集合父类
4.1:定义
是所有集合的父接口;是在计算机中用于存储一种或多种引用数据类型,并且长度可变的容器;
4.2:常用方法
方法 | 说明 |
---|---|
boolean add(E e) | 向集合中添加元素,成功返回 true |
boolean addAll(Collection c) | 把 c 所有的元素添加到集合中 |
boolean remove(Object o) | 从集合中删除对象,有多个时删除第一个 |
boolean removeAll(Collection<?> c) | 删除与 c 相同的全部元素 |
boolean retainAll(Collection<?> c) | 删除与 c 不相同的全部元素 |
void clear() | 清空集合里的所有元素 |
boolean contains(Object o) | 判断是否包含指定元素 |
boolean isEmpty() | 判断是否为空 |
int size() | 返回集合里面的元素个数 |
Object[] toArray() | 把集合换成一个数组 |
Iterator iterator() | 返回一个 Iterator 对象,用于迭代集合 |
05:Arrays 工具类使用
5.1:Arrays 提供了对数组操作的各种方法
>>>:public static String toString(int[] a); :把数组转成字符串
>>>:public static void sort(int[] a) :对数组进行升序排序
// 以下使用 Arrays 实现数组排序
public class arraydemo{
public static void main(String[] args){
//定义一个数组
int[] arr = {24, 69, 80, 57, 13};
//public static String toString(int[] a) : 把数组转化为字符串
System.out.println("排序前:" + Arrays.toString(arr));
//public static void sort(int[] a) : 对数组进行升序排列
Arrays.sort(arr);
//排序后
System.out.println("排序后:" + Arrays.toString(arr));
}
}
Ninth:HashMap 集合
01:Map 集合的常用方法
1.1:Map
定义:Map是一个容器,容器框架中以键值对的形式存储元素的容器;
Map 接口常用方法
方法名 | 作用 |
---|---|
boolean containsKey(Object key) | 查询 Map 中是否包含 key |
boolean containsValue(object value) | 查询 Map 中是否包含 value |
V get(Object key) 只能通过键找值 | 查询指定 key 对应的 value |
isEmpty() | 查询 Map 是否为空 |
V put(K key , V value) | 添加数据 |
int size() | 获取键值对个数 |
Set<Map.Entry<K,V>> entrySet() | 返回此 Map 中包含的映射的 Set 集合 |
Set<K> keySet() | 返回所有键的 Set 集合 |
Collection<V> values() | 返回所有值的 Collection 集合 |
V remove(Object key); | 只能根据 key 删除元素 |
1.2:Map 容器的实现类
1.3:Map 容器通过 Map 接口实现
Map接口时一种键值对映射的 数据结构接口
1.4:HashMap
HashMap 的定义:基于哈希表实现,线程不安全的 Map 容器
HashMap 的常用方法:
方法名 | 作用 |
---|---|
Object put(Object key, Object value) | 添加数据,如果地图中先前包含该键的映射,则替换该值 |
Object get(Object key) | 返回指定键所映射的值 |
Set<Map.Entry<K,V>> entrySet() | 返回此地图中包含的映射的 Set 集合 |
Set<K> keySet() | 返回此地图中包含的键的 Set 集合 |
Collection<V> values() | 返回此地图中包含的值的 Collection 集合 |
V remove(Object key) | 从该地图中删除指定键的映射 |
// HashMap 应用
public class School{
private String name;
private String address;
//无参构造
public School(){
}
//含参构造
public School(String name, String address){
super();
this.name = name;
this.address = address;
}
//重写 toString 方法
public String toString(){
return "School [ name=" + name + ", address=" + address + "]"
}
//省略了 get & set 方法
//定义对象,添加元素及输出
HashMap<Integer, School> map = new HashMap<>();
School s1 = new School("中央戏剧学院", "北京海淀");
School s2 = new School("上海戏剧学院", "上海静安");
School s3 = new School("北京电影学院", "北京市");
map.put(1001, s1);
map.put(1002, s2); //put方法存储数据并输出
map.put(1003, s3);
//遍历键值对输出
Set<Map.Entry<Intrger, School>> entrySet = map.entrySet();
for(Map.Entry<Integer,School> o:entrySet){
System.out.println(o.getKey()+":"+o.getValue());
}
}
//-------------
//输出的结果
1001:School [name=中央戏剧学院, address=北京海淀]
// keySet() 遍历输出键
Set key = map.keySet();
for(Object o:key){
System.out.println(o);
}
// 输出的结果
1001
1002
1003
02:HashMap 的使用
03:HashMap 原理
3.1:HashMap 的运行原理
HashMap 底层结构:是基于哈希表(数组和链表)实现
》》:HashMap 底层是 hash 数组和单项链表实现,数组中的每个元素都是链表,由 Node 内部类(实现 Map.Entry接口)实现。
》》:存储对象时,将 K / V 键值对传给 put() 方法
》①:调用 hash(K) 方法计算 K 的 hash 值,然后结合数组长度,计算得数组下标;
》②:调整数组大小(当容器中的元素个数大于 capacity*loadFactor 时,容器会进行扩容 resize 为2n)
注意事项:每次扩容都需要重建 hash 表,所以频繁的扩容是非常影响性能的,为了避免集合频繁扩容造成的性能问题,我们需要在集合初始化的时候指定集合容量的大小;
HashMap 的运行原理
04:hashCode 和 equals 方法原理
注意:如果 key 值为两个相同的对象内容,map 添加的时候,两个对象都能添加并且也能输出,违背了 key 值的唯一性,可以通过重写 hashCode() 和 equals 方法实现;
// 将相同的内容对象作为 key 的值
public static void main(String[] args){
HashMap<School, Integer> map = new HashMap<>();
School s1 = new School("中央戏剧学院", "北京海淀区");
School s2 = new School("中央戏剧学院", "北京海淀区"); //将相同的内容对象作为 key 的值
map.put(s1, 1001);
map.put(s2, 1885);
Set<Map.Entry<School,Integer>> entrySet = map.entrySet();
for(Map.Entry<School,Integer> o:entrySet){
System.out.println(o.getKey() + ":" + o.getValue);
}
}
//-*-*-*-*-*-*-*-*-*-*- // 输出的内容
// 违背 key 的唯一性
School{address='中央戏剧学院', name='北京海淀区'}:1001
School{address='中央戏剧学院', name='北京海淀区'}:1885
解决问题:
①:重写 hashCode(),保证两个具有相同属性对象的 hashCode 相同;
②:重写 equal(),一般会根据实际业务内容来定义,根据对象属性来判断两个对象是否相等;
注意事项:
Ⅰ:在 Java 应用程序运行时,无论何时多次调用同一个对象时的 hashCode() 方法,这个对象的 hashCode() 方法返回值必须是相同的一个 int 值;
Ⅱ:如果两个对象 equals() 返回的值为 true ,则他们的 hashCode() 也必须返回相同的 int 值。
重写 hashCode() 方法:
// 重写 hashCode() 方法
public int hashCode(){
final int prime = 31;
int result = 1;
//将类中的属性进行特定的运算
result = prime * result((address == null) ? 0:address.hashCode());
result = prime * result((name == null) ? 0:address.hashCode());
return result; //返回计算好的 hash 值,确定元素在数组中的存储位置
}
重写 equals() 方法:
// 重写 equals 方法
public boolean equals(Object obj){
if(this == obj){
return true;
}
if(this == null){
return false;
}
if(getClass() != pbj.getClass()){
return false;
}
School other = (School)obj;
if(address == null){
if(other.address != null){
return false;
}else if(!address.equals(other.address)){ //比较对象属性 address 是否相等
return false;
}
}
if(name == null){
if(other.name != null){
return false;
}else if(!name.equals(other.name)){ //比较对象属性 name 是否相等
return false;
}
return true;
}
}
注意事项:
Ⅰ:没有重写 Object 的 equals 方法,则调用 Object 的 equals 方法,比较的是对象在堆中的地址是否相等;
Ⅱ:重写了 equals 方法,一般用来比较对象的属性是否相等;
Tenth:IO 编程
01:文件类 File 类的使用
1.1:概念:
File 是用来操作文件 或 目录属性 而不可以操作文件内容的类;
1.2:File 类的构造方法
方法 | 说明 |
---|---|
File(String pathname) | pathname 参数指定文件位置 |
File(String dir , String subpath) | dir 参数指定目录的路径 subpath 参数指定文件名 |
File(File parent , String subpath) | parent 参数指定目录路径 subpath 参数指定文件名 |
注意事项:File 构造方法必须指定参数;
File 类常用方法:
// 代码实现
public class fileTest{
public static void main(String[] args){
//创建 File 对象 dir ,以 D 盘路径作为方法的参数
File dir = new File("D:\\");
//通过对象 dir 的list 方法获得该目录下的所有文件及目录,并保存在数组中
File[] files = dir.listFiles();
//遍历数组
for(File f:files){
//通过 File 类的 isFile 方法判断是否是文件
if(f.isFile()){
//如果是文件,通过 String 类的常用方法判断该文件是否是 mp3 格式的文件
if(f.getName().endsWith(".mp3")){
//向控制台输出满足条件的文件名称
System.out.println(f.getName());
}
}
}
}
}
02:IO 流的概念和分类
2.1:流的概念和作用
流是一组有顺序的、有起点和终点的字节集合,是对数据传输的总称或抽象。即数据在两个设备间的传输称之为流,流的本质是数据传输,根据数据传输的特性将流抽象为各种类,方便更直观的进行数据操作;
2.2:字符流 和 字节流
字符流的由来:因为数据编码不同,所以有了对字符进行高效操作的流对象。本质其实就是基于字节流读取时,去查了指定的 ASCII 码表;
字节流和字符流的区别:
Ⅰ:读写单位不同:字节流以字节(8bit) 为单位,字符流以字符为单位,根据码表映射字符,一次可以读多个字节。
Ⅱ:处理对象不同:字节流能处理所有类型的数据(如:图片、avi等等),而字符流只能处理字符类型的数据;
Ⅲ:按数据单元分类:字节流操作的最小单元为 8位的字节;字符流操作的最小单元是 16位的字符;
Ⅳ:字节流和字符流的区分非常简单,字节流建议用于二进制数据;字符流用于文本,两者的用法基本类似;
注意事项:①:只要是处理纯文本的数据,就优先考虑使用字符流,除此之外都使用字节流;②:bit(比特位) 才是数据真正的最小单位!
2.3:输入流 和 输出流
对输入流只能进行读操作,对输出流只能进行写操作,程序中需要根据待传输数据的不同特性 而使用不同的流;
2.4:IO 流分类
①:输入字节流 (InputStream)
②:输出字节流 (OutputStream)
③:输入字符流(Reader)
④:输出字符流(Writer)
注意事项:这四个类都是抽象类,所以不能 实例化对象,只能通过他们的子类来进行实例化;
03:文件字节流 和 文件字符流的使用
3.1:输入字节流 (InputStream )抽象类的常用方法
输入字节流:InputStream 抽象类的常用方法;
方法 | 说明 |
---|---|
int read() | 读取单个字节到程序,以 int 型返回所读取的字节数据,返回值为“-1” 表示读取到了文件的末尾 |
int read(byte[] b) | 读取多个字节到程序,并存储在字节数组中,返回实际读取的字节数,返回值为 “-1” 表示读取到了文件的末尾 |
void close() | 关闭输入流 |
注意事项:字节流是以字节(8位)为数据单元的 IO 流;
//文件输入流的演示
public static void main(String[] args){
//创建 FileInputStream 对象
FileInputStream fis = new FileInputStream("D:hello.txt");
//创建 BufferedInputStream 对象
BufferedInputStream bis = new BufferedInputStream(fis); //缓冲流
//通过read方法读取文件内容
byte[] buffer = new byte[1024]; //缓冲区大小
int hasRead = 0;
String result = "";
while((hasRead=bisread(buffer)) != -1){
result += new String(buffer, 0, hasRead);
}
System.out.println(result);
//通过 close() 方法关闭流
bis.close();
}
3.2:输出字节流(OutputStream) 抽象类的常用方法
方法 | 说明 |
---|---|
void write(int b) | 将指定的字节输出到输出流中 |
void write(byte[] b) | 将字节数组输出到输出流中 |
void close() | 关闭输出流 |
3.3:输入字符流(Reader) 抽象类的常用方法
方法 | 说明 |
---|---|
int read() | 读取单个字符,当返回值为“-1” 时,表示读取到了文件的末尾 |
int read(char[] c) | 读取多个字符,保存到字符数组 c 中,返回实际读取的字符数,当返回值为“-1” 时表示读取到了文件的末尾 |
void close() | 关闭输入流 |
注意事项:字符流是以字符(16位)为数据单元的 IO 流
3.4:输入字符流(Writer) 类的常用方法
方法 | 说明 |
---|---|
void write(String str) | 将 str 字符串里包含的字符输出到指定的输出流中 |
void close() | 关闭输出流 |
void flush() | 清空流 |
04:文件缓冲流的使用
4.1:含义:
缓冲处理流是以 提高读写性能为目的的,具备 缓冲区的处理流
4.2:缓冲流的优势:
①:不带缓冲流的工作原理:它读取到一个字节/字符,就向用户指定的路径写出去读一个写一个,所以效率慢了;
②:带缓冲流的工作原理:读取到一个字节/字符,先不输出,等达到缓冲区默认大小时再一次性将缓冲区默认大小的数据,从而提高了工作效率;
③:用缓冲流来完成数据的 IO 操作,进而提高了读写的效率;
4.3:BufferedInputStream 字节缓冲输入流类的常用方法
通过 BufferedInputStream 类读取文件的内容
4.4:BufferedOutputStream 字节缓冲输出流类的常用方法
05:NIO 类库 和 BIO 类库的区别
5.1:IO 的分类
Ⅰ:阻塞 / 非阻塞
Ⅱ:同步 / 异步 (消息通讯的一种机制)
BIO(传统 IO) | NIO(新 IO) | AIO(NIO2) | |
---|---|---|---|
JDK版本 | 1.4 之前 | 1.4 | 1.7 |
阻塞 | 是 | 否 | 否 |
同步 | 是 | 是 | 否(异步) |
①:之前学习的 IO 又称 BIO;
②:JDK1.4时出现 NIO (又称之为新 IO );
③:JDK1.7时出现 AIO(Asynchronous IO ,异步 IO ,又称之为 NIO2.0 );
④:新 IO 引入非阻塞、异步的特性,使得 IO 体系更加丰富,可以运用于更多的应用场景。
5.2:BIO 和 NIO 的对比
NIO 可以用于文件 IO 和 网络 IO ,我们以 NIO 在文件中的操作为例,通过 FileChannel + Buffer 操作文件 IO (FileChannel 为文件通道,buffer 为缓冲区)
5.3:Buffer 类的常用方法
5.4:FileChannel 类的常用方法
Eleventh:多线程
01:创建线程的方式
1.1:多线程概述
线程定义:线程是进程中系统进行调度的最小执行单位
多线程:是实现多个线程并发执行的技术
进程:进程是由程序、数据及系统资源组成的一次程序运行的实体;
线程:一个程序的多个运行实例对应多个进程,每个进进程包含一个或多个线程
1.2:如何使用线程
02:线程的生命周期
03:线程控制方法
Twelfth :线程池
01:线程池的使用
1.1:什么是线程池(概念)
线程池是符用线程以提高响应速度,减少系统创建和销毁线程开销的技术!
①:提交任务的线程相当于生产者;②执行任务的线程相当于消费者
1.2:线程池概念图
02:创建固定大小线程池
2.1:固定大小线程池
限定核心线程数和最大线程数为相同固定值的线程池
注意事项:所有工作线程都是核心线程,数量固定
2.2:固定大小线程池使用步骤
Ⅰ:定义任务类,实现 Runnable 或 Callable 接口
//定义任务类
public class RunnableTask implements Runnable{......}
// 或者
public class CallableTask implements Callable<String{......}
Ⅱ:创建固定大小的线程池
//创建固定大小的线程池
ExecutorService fTdPool = Executors.newFixedThreadExecutor(nThreads);
Ⅲ:创建任务,提交给线程池
//创建任务,提交给线程池
fTdPool.execute(new RunnableTask(i));
//或者
submit(new CallableTask(i));
Ⅳ:关闭线程池,停止接受新任务
//关闭线程池
fTdPool.shutdown();
2.3:固定大小线程池的应用
2.3.1:定义 Callable 任务类
public class ThreadsPools implements Callable<String>{
private int taskNo;//任务编号
//省略构造方法
@Override
public String call() throws Exception {
String tdName = Thread.currentThread().getName();
System.out.println("CTask "+taskNo+"is Running in "+tdName);
try {
Thread.sleep(5000);
}catch(InterruptedExecption e){
e.printStackTrace();
}
System.out.println("CTask "+taskNo+" end");
return "CallableTask"+taskNo;
}
}
2.3.2:线程池测试类 ThreadPoolTest
//线程池测试类
public class ThreadPoolTest {
public static void main(String[] args) {
//step1 创建【固定大小线程】线程池
ExecutorService fTdPool = Executor.newFixedThreadPool(3);
//step2 提交任务
for(int i=0;i<10;i++) {
fTdPool.execute(new RunnableTask(i));
fTdPool.submit(new CallableTask(i));
}
//step3 关闭线程池
fTdPool.shutdown();
}
}
03:创建缓存线程池
定义:可回收闲置线程的、按需控制线程数量的线程池;
注意事项:没有核心线程,空闲超过指定时间就回收;
3.1 缓存线程池的使用步骤
Ⅰ:定义任务类:实现 Runnable 或 Callable 接口
//定义任务类
public class RunnableTask implements Runnable{......}
//或
public class CallableTask implements Callable<Srting {......}
Ⅱ:创建缓存线程池
//创建缓存线程池
ExeccutorService cTdPool = Exectors.newCachedThreadPool();
Ⅲ:创建任务,提交给线程池
//创建任务,提交给线程池
cTdPool.execute(new RunnableTask(i));
//或
submit(new CallableTask(i));
Ⅳ:关闭线程池,停止接受新任务
//关闭线程池
fTdPool.shutdown();
04:线程池拒绝策略
4.1:定义
线程池拒绝策略:是指当任务添加到线程池中被拒绝,而采取的处理措施;
4.2:线程池拒绝测类类型
拒绝策略类型 | 说明 |
---|---|
ThreadPoolExecutor.AbortPolicy | 默认拒绝策略,拒绝任务并抛出任务 |
ThreadPoolExecutor.CallerRunsPolicy | 使用调用线程直接运行任务 |
ThreadPoolExecutor.DiscardPolicy | 直接拒绝任务,不抛出错误 |
ThreadPoolExecutor.DiscardOldestPolicy | 触发拒绝策略,只要还有任务新增,一直会丢弃阻塞队列的最老任务,并将新的任务加入 |
4.3:
// DiscardOrderstPolicy 丢弃策略
//丢弃最老的请求,尝试再次提交当前任务
RejectExecutionHandler handler = new ThreadPoolExecutor.DiscardOldesetPolicy();
//DiscardPolicy 丢弃策略
//用于被拒绝的任务的处理程序,默认情况下它将丢弃的被拒绝的任务
RejectedExecutionHandler handler = new ThreadPoolExecutor.DiscardPolicy();
Thirteenth:线程安全
01:线程安全问题
1.1 : 什么是线程安全
· 代码在多个线程同时运行时存在不确定性,结果不符合预期的现象;
· 存在这种问题的代码,称为线程不安全代码
示例:
1.2:线程安全问题产生的原因
多个线程共享同一个资源;多个线程都对共享资源进行修改;多个线程同时对共享资源进行修改;
注意事项:线程互斥是防止多个线程同时访问一个公共资源从而保障数据完整性的机制;
1.3:线程同步问题
Java 提供的同步机制
①:synchronized 隐式同步锁
②:Lock 同步锁
02:synchronized 解决线程安全问题
2.1:隐式同步锁(synchronized)
--- 是指Java 中使用 synchronized 修饰的方法和代码块实现线程互斥的机制
//同步方法
public synchronized int length(){
return count;
}
public void println(String x){ //同步监视器
synchronized(this){ //同步代码块
print(x);
newLine();
}
}
注意事项:synchronized 可以修饰除构造方法外的所有方法;
其他修饰符:synchronized 返回值类型 方法名(参数列表);
public class Station implements Runnable{
int ticket = 20;
@Override
public void run() {
while(ticket>0) { //同步锁
synchronized(this) {//this当前对象
if(ticket>0) {
System.out.println(Thread.currentThread().getName()+"卖出第"+(21-ticket--)+"张票");
}else {
System.out.println("票已售完,下次再来");
System.exit(0);
}
}
try {
Thread.sleep(50);
}catch(InterruptedException e) {
e.printStackTrace();
}
}
}
//创建三个线程
public static void main(String[] args) {
Station station = new Station();
Thread a , b , c;
a = new Thread(station,"窗口①");
b = new Thread(station,"窗口②");
c = new Thread(station,"窗口③");
a.start();
b.start();
c.start();
}
}
2.2:同步方法的作用
①:synchronized 修饰静态方法时:对于同一个对象,同一时刻只能有一个线程执行该方法;
②:synchronized修饰静态方法时:静态方法上的 synchronized 为类级别,同一时刻只有一个线程可以执行该类下的任意一个 synchronized 方法;
了解部分:
volatile 关键字的使用:
*
// public class HelloVolatile { //不加 Volatile 会发现程序在 1 毫秒之后,线程还在继续执行,说明并没有把 flag 及时变成false //加了 Volatile 之后,1毫秒之后,线程结束 public void m(){ System.out.println("开始执行"); while(flag){ } System.out.println("结束"); } }
// public class demo{ static HelloVolatile hellovolatile = new HelloVolatile(); public static void main(String[] args){ Thread thread = new Thread( ()->{hellovolatile.m()} ); thread.start(); try { TimeUnit.SECONDS.sleep(1); }catch(InterruptedException e){ e.printStackTrace(); } hellovolatile.flag = false; } }
* 以上代码:定义一个类,启动一个线程来跑这个类;
* 在主线程 main 方法里面,在执行 1 毫秒以后,把 flag 设成 false ,在 flag 不加 volatile 的情况下,程序在 1 毫秒之后未停止;
* 说明 main 线程更改的 flag 并没有及时的同步到新线程里面,加 volatile 则在 1 毫秒之后停止
2.3:synchronized 关键字和 volatile 的区别
03:Lock 同步锁
//代码实现
import java.util.concurrent.locks.ReentrantLock;
public class SellTicket {
//测试 Lock 锁
public static void main(String[] args) {
TestLock testlock = new TestLock();
new Thread(testlock).start();
new Thread(testlock).start();
new Thread(testlock).start();
}
}
//线程=============================================================
class TestLock implements Runnable{
int ticketNum = 10;
private final ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while(true) {
lock.lock();//加锁
try {
if(ticketNum>0) {
try {
Thread.sleep(1000);
}catch(InterruptedException e) {
e.printStackTrace();
}
System.out.println("第"+(ticketNum--)+"张票售出!");
}
else break;
}finally {
lock.unlock();//解锁
}
}
}
}
3.1:synchronized 和 ReentrantLock 的相同点
①:都是用来协调多线程对共享对象,变量的访问;
②:都可重入锁,同一线程可以多次获得同一个锁;
③:都保证了可见性和互斥性
3.2:synchronized 和 ReentrantLock 的不同点
Ⅰ:ReentrantLock 显式获得释放锁,synchronized隐式获得释放锁;
Ⅱ:ReentrantLock 可响应中断,synchronized 不可以响应中断;
Ⅲ:ReentrantLock 是 API 级别的,synchronized 是JVM 级别的;
Ⅳ:ReentrantLock 可以实现公平锁;
Ⅴ:ReentrantLock 可以通过 Condition 可以绑定多个条件;
Ⅵ:Lock 是一个接口,而 synchronized 是Java中的关键字,synchronized 是内置的语言实现。