类
LOL有很多英雄,比如盲僧,团战可以输,提莫必须死,盖伦,琴女 所有这些英雄,都有一些共同的状态 比如,他们都有名字,hp,护甲,移动速度等等 这样我们就可以设计一种东西,叫做**类 **,代表英雄这样一种事物。
类: 英雄(Hero) 。
状态: 名字, 血量,护甲,移动速度 。
public class Hero {
String name; //姓名
float hp; //血量
float armor; //护甲
int moveSpeed; //移动速度
}
类就像一个模板,根据这样一个模板,可以创建一个个的具体的英雄 一个个具体的英雄,就叫一个个的对象 new Hero() 就是java中创建一个英雄对象的意思。
public class Hero {
String name; //姓名
float hp; //血量
float armor; //护甲
int moveSpeed; //移动速度
public static void main(String[] args) {
Hero garen = new Hero();
garen.name = "盖伦";
garen.hp = 616.28f;
garen.armor = 27.536f;
garen.moveSpeed = 350;
Hero teemo = new Hero();
teemo.name = "提莫";
teemo.hp = 383f;
teemo.armor = 14f;
teemo.moveSpeed = 330;
}
}
主函数
public class helloworld{
public static void main(String[] args){
}
}
输入输出
1.输入需要在开头引入import java.util.Scanner;
然后以输入int类型为例
Scanner s = new Scanner(System.in);
int num = s.nextInt();
同样,如果输入double类型就double num = s.nextDouble();
如果已经在输入字符串之前输入过整型浮点型等,需要连着用两次输入,因为上一次输入后的回车也会被读取。
2.输出System.out.println();
变量final
当一个变量被final修饰的时候,该变量只有一次赋值的机会
public class HelloWorld {
public void method1() {
final int i = 5;
i = 10; //i在第4行已经被赋值过了,所以这里会出现编译错误
}
}
public class HelloWorld {
public void method1() {
final int i;
i = 10; //i在第4行,只是被声明,但是没有被赋值,所以在这里可以进行第一次赋值
i = 11; //i在第6行已经被赋值过了,所以这里会出现编译错误
}
}
数组
public class HelloWorld {
public static void main(String[] args) {
//声明一个引用
int[] a; //写成a[]也可以
//创建一个长度是5的数组,并且使用引用a指向该数组
a = new int[5];
int[] b = new int[5]; //声明的同时,指向一个数组
}
}
不进行初始化,默认值为0
数组长度
求法:a.length
(a为数组名)
复制数组
System.arraycopy(src, srcPos, dest, destPos, length)
src: 源数组
srcPos: 从源数组复制数据的起始位置
dest: 目标数组
destPos: 复制到目标数组的起始位置
length: 复制的长度
public class HelloWorld {
public static void main(String[] args) {
int a [] = new int[]{18,62,68,82,65,9};
int b[] = new int[3];//分配了长度是3的空间,但是没有赋值
//通过数组赋值把,a数组的前3位赋值到b数组
//方法一: for循环
for (int i = 0; i < b.length; i++) {
b[i] = a[i];
}
//方法二: System.arraycopy(src, srcPos, dest, destPos, length)
//src: 源数组
//srcPos: 从源数组复制数据的起始位置
//dest: 目标数组
//destPos: 复制到目标数组的启始位置
//length: 复制的长度
System.arraycopy(a, 0, b, 0, 3);
//把内容打印出来
for (int i = 0; i < b.length; i++) {
System.out.print(b[i] + " ");
}
}
}
Arrays数组
Arrays是针对数组的工具类,可以进行 排序,查找,复制填充等功能。 大大提高了开发人员的工作效率。
需要引入import java.util.Arrays;
-
数组复制
与使用System.arraycopy进行数组复制类似的, Arrays提供了一个copyOfRange方法进行数组复制。
不同的是System.arraycopy,需要事先准备好目标数组,并分配长度。 copyOfRange 只需要源数组就就可以了,通过返回值,就能够得到目标数组了。
除此之外,需要注意的是 copyOfRange 的第3个参数,表示源数组的结束位置,是取不到的。import java.util.Arrays; public class HelloWorld { public static void main(String[] args) { int a[] = new int[] { 18, 62, 68, 82, 65, 9 }; // copyOfRange(int[] original, int from, int to) // 第一个参数表示源数组 // 第二个参数表示开始位置(取得到) // 第三个参数表示结束位置(取不到) int[] b = Arrays.copyOfRange(a, 0, 3); for (int i = 0; i < b.length; i++) { System.out.print(b[i] + " "); } } }
-
转换为字符串
如果要打印一个数组的内容,就需要通过for循环来挨个遍历,逐一打印 但是Arrays提供了一个toString()方法,直接把一个数组,转换为字符串,这样方便观察数组的内容。
import java.util.Arrays; public class HelloWorld { public static void main(String[] args) { int a[] = new int[] { 18, 62, 68, 82, 65, 9 }; String content = Arrays.toString(a); System.out.println(content); } }
-
排序
Arrays工具类提供了一个sort方法,只需要一行代码即可完成排序功能。
import java.util.Arrays; public class HelloWorld { public static void main(String[] args) { int a[] = new int[] { 18, 62, 68, 82, 65, 9 }; System.out.println("排序之前 :"); System.out.println(Arrays.toString(a)); Arrays.sort(a); System.out.println("排序之后:"); System.out.println(Arrays.toString(a)); } }
-
搜索
查询元素出现的位置 需要注意的是,使用binarySearch进行查找之前,必须使用sort进行排序 如果数组中有多个相同的元素,查找结果是不确定的 。
import java.util.Arrays; public class HelloWorld { public static void main(String[] args) { int a[] = new int[] { 18, 62, 68, 82, 65, 9 }; Arrays.sort(a); System.out.println(Arrays.toString(a)); //使用binarySearch之前,必须先使用sort进行排序 System.out.println("数字 62出现的位置:"+Arrays.binarySearch(a, 62)); } }
-
判断是否相同
比较两个数组的内容是否一样 第二个数组的最后一个元素是8,和第一个数组不一样,所以比较结果是false
import java.util.Arrays; public class HelloWorld { public static void main(String[] args) { int a[] = new int[] { 18, 62, 68, 82, 65, 9 }; int b[] = new int[] { 18, 62, 68, 82, 65, 8 }; System.out.println(Arrays.equals(a, b)); } }
-
填充
使用同一个值,填充整个数组 。
import java.util.Arrays; public class HelloWorld { public static void main(String[] args) { int a[] = new int[10]; Arrays.fill(a, 5); System.out.println(Arrays.toString(a)); } }
类和对象
继承
Weapon继承Item, 虽然Weapon自己没有设计name和price,但是通过继承Item类,也具备了name和price属性
public class Weapon extends Item{
int damage; //攻击力
public static void main(String[] args) {
Weapon infinityEdge = new Weapon();
infinityEdge.damage = 65; //damage属性在类Weapon中新设计的
infinityEdge.name = "无尽之刃";//name属性,是从Item中继承来的,就不需要重复设计了
infinityEdge.price = 3600;
}
}
#####重载
有一种英雄,叫做物理攻击英雄 ADHero 为ADHero 提供三种方法
public void attack()
public void attack(Hero h1)
public void attack(Hero h1, Hero h2)
方法名是一样的,但是参数类型不一样 在调用方法attack的时候,会根据传递的参数类型以及数量,自动调用对应的方法
public class ADHero extends Hero {
public void attack() {
System.out.println(name + " 进行了一次攻击 ,但是不确定打中谁了");
}
public void attack(Hero h1) {
System.out.println(name + "对" + h1.name + "进行了一次攻击 ");
}
public void attack(Hero h1, Hero h2) {
System.out.println(name + "同时对" + h1.name + "和" + h2.name + "进行了攻击 ");
}
public static void main(String[] args) {
ADHero bh = new ADHero();
bh.name = "赏金猎人";
Hero h1 = new Hero();
h1.name = "盖伦";
Hero h2 = new Hero();
h2.name = "提莫";
bh.attack(h1);
bh.attack(h1, h2);
}
}
如果要攻击更多的英雄,就需要设计更多的方法,这样类会显得很累赘,像这样:
public void attack(Hero h1)
public void attack(Hero h1,Hero h2)
public void attack(Hero h1,Hero h2,Hero h3)
**这时,可以采用可变数量的参数 只需要设计一个方法 public void attack(Hero …heros) 即可代表上述所有的方法了 在方法里,使用操作数组的方式处理参数heros即可 **
public class ADHero extends Hero {
public void attack() {
System.out.println(name + " 进行了一次攻击 ,但是不确定打中谁了");
}
// 可变数量的参数
public void attack(Hero... heros) {
for (int i = 0; i < heros.length; i++) {
System.out.println(name + " 攻击了 " + heros[i].name);
}
}
public static void main(String[] args) {
ADHero bh = new ADHero();
bh.name = "赏金猎人";
Hero h1 = new Hero();
h1.name = "盖伦";
Hero h2 = new Hero();
h2.name = "提莫";
bh.attack(h1);
bh.attack(h1, h2);
}
}
构造方法
通过一个类创建一个对象,这个过程叫做实例化
实例化是通过调用构造方法(又叫做构造器)实现的
-
如果不写,就会默认提供一个
public class Hero { String name; //姓名 float hp; //血量 float armor; //护甲 int moveSpeed; //移动速度 //这个无参的构造方法,如果不写, //就会默认提供一个无参的构造方法 // public Hero(){ // System.out.println("调用Hero的构造方法"); // } public static void main(String[] args) { Hero garen = new Hero(); garen.name = "盖伦"; garen.hp = 616.28f; garen.armor = 27.536f; garen.moveSpeed = 350; Hero teemo = new Hero(); teemo.name = "提莫"; teemo.hp = 383f; teemo.armor = 14f; teemo.moveSpeed = 330; } }
-
一旦提供了一个有参的构造方法 同时又没有显式的提供一个无参的构造方法 那么默认的无参的构造方法,就没有了
public class Hero { String name; //姓名 float hp; //血量 float armor; //护甲 int moveSpeed; //移动速度 //有参的构造方法 //默认的无参的构造方法就失效了 public Hero(String heroname){ name = heroname; } public static void main(String[] args) { Hero garen = new Hero("盖伦"); Hero teemo = new Hero(); //无参的构造方法“木有了” } }
this
this这个关键字,相当于普通话里的“我”
小明说 “我吃了” 这个时候,“我” 代表小明
小红说 “我吃了” 这个时候,“我” 代表小红
"我"代表当前人物
this即代表**当前对象 **
包
包: package
把比较接近的类,规划在同一个包下
**使用同一个包下的其他类,直接使用即可 但是要使用其他包下的类,必须import **
package charactor;
//Weapon类在其他包里,使用必须进行import
import property.Weapon;
public class Hero {
String name; //姓名
float hp; //血量
float armor; //护甲
int moveSpeed; //移动速度
//装备一把武器
public void equip(Weapon w){
}
}
访问修饰符
成员变量有四种修饰符
private 私有的
package/friendly/default 不写
protected 受保护
public 公共的
类之间的关系
类和类之间的关系有如下几种:
以Hero为例
自身:指的是Hero自己
同包子类 :ADHero这个类是Hero的子类,并且和Hero处于同一个包下
不同包子类 :Support这个类是Hero的子类,但是在另一个包下 同包类 : GiantDragon 这个类和Hero是同一个包,但是彼此没有继承关系
其他类:Item这个类,在不同包,也没有继承关系的类
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PZHEtZXt-1610859583833)(C:\Users\86156\Pictures\605.png)]
private 私有的
使用private修饰属性
自身:可以访问
同包子类:不能继承
不同包子类:不能继承
同包类:不能访问
其他包类:不能访问
package/friendly/default 不写
没有修饰符即代表package/friendly/default
自身:可以访问
同包子类:可以继承
不同包子类:不能继承
同包类:可以访问
其他包类:不能访问
protected 受保护的
自身:可以访问
同包子类:可以继承
不同包子类:可以继承
同包类:可以访问
其他包类:不能访问
######public 公共的
自身:可以访问
同包子类:可以继承
不同包子类:可以继承
同包类:可以访问
其他包类:可以访问
总结
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-E0qfWgMj-1610859583835)(C:\Users\86156\Pictures\612.png)]
注:红色为不可以
修饰符的使用情况
- 属性通常使用private封装起来
- 方法一般使用public用于被调用
- 会被子类继承的方法,通常使用protected
- package用的不多,一般新手会用package,因为还不知道有修饰符这个东西
再就是作用范围最小原则 ,简单说,能用private就用private,不行就放大一级,用package,再不行就用protected,最后用public。 这样就能把数据尽量的封装起来,没有必要露出来的,就不用露出来了 。
类属性
当一个属性被static 修饰的时候,就叫做类属性,又叫做静态属性。
当一个属性被声明成类属性,那么所有的对象,都共享一个值
与对象属性对比: 不同对象的 对象属性 的值都可能不一样。
比如盖伦的hp 和 提莫的hp 是不一样的。
但是所有对象的类属性的值,都是一样的
类属性
类属性 : 又叫做静态属性
对象属性: 又叫实例属性,非静态属性
如果一个属性声明成类属性,那么所有的对象,都共享这么一个值
给英雄设置一个类属性叫做“版权" (copyright), 无论有多少个具体的英雄,所有的英雄的版权都属于 Riot Games公司。
package charactor;
public class Hero {
public String name; //实例属性,对象属性,非静态属性
protected float hp;
static String copyright;//类属性,静态属性
public static void main(String[] args) {
Hero garen = new Hero();
garen.name = "盖伦";
Hero.copyright = "版权由Riot Games公司所有";
System.out.println(garen.name);
System.out.println(garen.copyright);
Hero teemo = new Hero();
teemo.name = "提莫";
System.out.println(teemo.name);
System.out.println(teemo.copyright);
}
}
访问类属性
访问类属性有两种方式
-
对象.类属性
teemo.copyright
-
类.类属性
Hero.copyright
这两种方式都可以访问类属性,访问即修改和获取,但是建议使用第二种 类.类属性 的方式进行,这样更符合语义上的理解
类方法
类方法 : 又叫做静态方法
对象方法 : 又叫实例方法,非静态方法
访问一个对象方法,必须建立在有一个对象 的前提的基础上
访问类方法,不需要对象 的存在,直接就访问
调用类方法
和访问类属性一样,调用类方法也有两种方式
-
对象.类方法
-
类.类方法
这两种方式都可以调用类方法,但是建议使用第二种 类.类方法 的方式进行,这样更符合语义上的理解。
并且在很多时候,并没有实例。
什么时候设计对象方法,什么时候设计类方法
-
如果在某一个方法里,调用了对象属性,比如
public String getName(){ return name; }
name属性是对象属性,只有存在一个具体对象的时候,name才有意义。 如果方法里访问了对象属性,那么这个方法,就必须设计为对象方法
-
如果一个方法,没有调用任何对象属性,那么就可以考虑设计为类方法,比如
public static void printGameDuration(){
System.out.println("已经玩了10分50秒");
}
printGameDuration 打印当前玩了多长时间了,不和某一个具体的英雄关联起来,所有的英雄都是一样的。 这样的方法,更带有功能性色彩
静态不能直接访问静态,因为再内存中是先有静态,后有非静态,“先人不知道后人,但是后人知道先人”
属性初始化
对象属性初始化
对象属性初始化有3种
- 声明该属性的时候初始化
- 构造方法中初始化
- 初始化块
package charactor;
public class Hero {
public String name = "some hero"; //声明该属性的时候初始化
protected float hp;
float maxHP;
{
maxHP = 200; //初始化块
}
public Hero(){
hp = 100; //构造方法中初始化
}
}
类属性初始化
类属性初始化有2种
-
声明该属性的时候初始化
-
静态初始化块
package charactor;
public class Hero {
public String name;
protected float hp;
float maxHP;
//物品栏的容量
public static int itemCapacity=8; //声明的时候 初始化
static{
itemCapacity = 6;//静态初始化块 初始化
}
public Hero(){
}
public static void main(String[] args) {
System.out.println(Hero.itemCapacity);
}
}
单例模式
LOL里有一个怪叫大龙GiantDragon,只有一只,所以该类,只能被实例化一次
单例模式
单例模式又叫做 Singleton模式,指的是一个类,在一个JVM里,只有一个实例存在
饿汉式单例模式
GiantDragon 应该只有一只,通过私有化其构造方法,使得外部无法通过new 得到新的实例。
GiantDragon 提供了一个public static的getInstance方法,外部调用者通过该方法获取12行定义的对象,而且每一次都是获取同一个对象。 从而达到单例的目的。
这种单例模式又叫做饿汉式单例模式 ,无论如何都会创建一个实例
package charactor;
public class GiantDragon {
//私有化构造方法使得该类无法在外部通过new 进行实例化
private GiantDragon(){
}
//准备一个类属性,指向一个实例化对象。 因为是类属性,所以只有一个
private static GiantDragon instance = new GiantDragon();
//public static 方法,提供给调用者获取12行定义的对象
public static GiantDragon getInstance(){
return instance;
}
}
懒汉式单例模式
懒汉式单例模式与饿汉式单例模式不同,只有在调用getInstance的时候,才会创建实例
package charactor;
public class GiantDragon {
//私有化构造方法使得该类无法在外部通过new 进行实例化
private GiantDragon(){
}
//准备一个类属性,用于指向一个实例化对象,但是暂时指向null
private static GiantDragon instance;
//public static 方法,返回实例对象
public static GiantDragon getInstance(){
//第一次访问的时候,发现instance没有指向任何对象,这时实例化一个对象
if(null==instance){
instance = new GiantDragon();
}
//返回 instance指向的对象
return instance;
}
}
什么时候使用饿汉式,什么时候使用懒汉式
饿汉式是立即加载的方式,无论是否会用到这个对象,都会加载。 如果在构造方法里写了性能消耗较大,占时较久的代码,比如建立与数据库的连接,那么就会在启动的时候感觉稍微有些卡顿。
懒汉式,是延迟加载的方式,只有使用的时候才会加载。 并且有线程安全的考量(鉴于同学们学习的进度,暂时不对线程的章节做展开)。 使用懒汉式,在启动的时候,会感觉到比饿汉式略快,因为并没有做对象的实例化。 但是在第一次调用的时候,会进行实例化操作,感觉上就略慢。
看业务需求,如果业务上允许有比较充分的启动和初始化时间,就使用饿汉式,否则就使用懒汉式
单例模式三元素
这个是面试的时候经常会考的点,面试题通常的问法是:
什么是单例模式?
回答的时候,要答到三元素
- 构造方法私有化
- 静态属性指向实例
- public static的 getInstance方法,返回第二步的静态属性
#####枚举类型
预先定义的常量
枚举enum是一种特殊的类(还是类),使用枚举可以很方便的定义常量 比如设计一个枚举类型 季节,里面有4种常量
public enum Season {
SPRING,SUMMER,AUTUMN,WINTER
}
一个常用的场合就是switch语句中,使用枚举来进行判断
注:因为是常量,所以一般都是全大写
public enum Season {
SPRING,SUMMER,AUTUMN,WINTER
}
public class HelloWorld {
public static void main(String[] args) {
Season season = Season.SPRING;
switch (season) {
case SPRING:
System.out.println("春天");
break;
case SUMMER:
System.out.println("夏天");
break;
case AUTUMN:
System.out.println("秋天");
break;
case WINTER:
System.out.println("冬天");
break;
}
}
}
使用枚举的好处
假设在使用switch的时候,不是使用枚举,而是使用int,而int的取值范围就不只是1-4,有可能取一个超出1-4之间的值,这样判断结果就似是而非了。(因为只有4个季节)
但是使用枚举,就能把范围死死的限定在这四个当中
接口与继承
接口
在设计LOL的时候,进攻类英雄有两种,一种是进行物理系攻击,一种是进行魔法系攻击 这时候,就可以使用接口来实现这个效果。
接口就像是一种约定,我们约定某些英雄是物理系英雄,那么他们就一定能够进行物理攻击。
物理攻击接口
创建一个接口 File->New->Interface
AD ,声明一个方法 physicAttack 物理攻击,但是没有方法体,是一个“空”方法
package charactor;
public interface AD {
//物理伤害
public void physicAttack();
}
设计一类英雄,能够使用物理攻击
设计一类英雄,能够使用物理攻击,这类英雄在LOL中被叫做AD 类:ADHero
继承了Hero 类,所以继承了name,hp,armor等属性
实现某个接口,就相当于承诺了某种约定
所以,实现了AD 这个接口,就必须 提供AD接口中声明的方法physicAttack() 实现在语法上使用关键字 **implements **
package charactor;
public class ADHero extends Hero implements AD{
public void physicAttack() {
System.out.println("进行物理攻击");
}
}
对象转型
引用类型与对象类型
首先,明确引用类型与对象类型的概念
在这个例子里,有一个对象 new ADHero(), 同时也有一个引用ad 对象是有类型的, 是ADHero
引用也是有类型的,是ADHero
通常情况下,引用类型和对象类型是一样的
接下来要讨论的类型转换的问题,指的是引用类型和对象类型 不一致 的情况下的转换问题
子类转父类(向上转型)
所谓的转型,是指当引用类型和对象类型不一致的时候,才需要进行类型转换
类型转换有时候会成功,有时候会失败
到底能否转换成功? 一个很简单的判别办法
把右边的当做左边来用,看说得通不
Hero h = new Hero();
ADHero ad = new ADHero();
h = ad;
右边ad引用所指向的对象的类型是 物理攻击英雄
左边h引用的类型 是 普通英雄
把物理攻击英雄 当做 普通英雄,说不说得通? 说得通,就可以转
所有的子类转换为父类 ,都是说得通的。
比如你身边的例子
苹果手机 继承了 手机,把苹果手机当做普通手机使用
怡宝纯净水 继承了 饮品, 把怡宝纯净水 当做饮品来使用
苍老师 继承了动物, 把苍老师 。。。
父类转子类(向下转型)
父类转子类,有的时候行,有的时候不行,所以必须进行强制转换。
强制转换的意思就是 转换有风险,风险自担。
**什么时候行呢 **
1. Hero h =new Hero();
2. ADHero ad = new ADHero();
3. h = ad;
4. ad = (ADHero) h;
第3行,是子类转父类,一定可以的
第4行,就是父类转子类,所以要进行强转。 h这个引用,所指向的对象是ADHero, 所以第4行,就会把ADHero转换为ADHero,就能转换成功。
**什么时候转换不行呢? **
1. Hero h =new Hero();
2. ADHero ad = new ADHero();
3. Support s =new Support();
4. h = s;
5. ad = (ADHero)h;
第4行,是子类转父类,是可以转换成功的
第5行,是把h引用所指向的对象 Support,转换为ad引用的类型ADHero。
从语义上讲,把物理攻击英雄,当成辅助英雄来用,说不通,所以会强制转换失败,并且抛出异常。
没有继承关系的两个类,互相转换
没有继承关系的两个类,互相转换,一定会失败
虽然ADHero和APHero都继承了Hero,但是彼此没有互相继承关系
“把魔法英雄当做物理英雄来用”,在语义上也是说不通的
实现类转换成接口(向上转型)
引用ad指向的对象是ADHero类型,这个类型实现了AD接口
package charactor;
public class Hero {
public String name;
protected float hp;
public static void main(String[] args) {
ADHero ad = new ADHero();
AD adi = ad;
}
}
10行: 把一个ADHero类型转换为AD接口
从语义上来讲,把一个ADHero当做AD来使用,而AD接口只有一个physicAttack方法,这就意味着转换后就有可能要调用physicAttack方法,而ADHero一定是有physicAttack方法的,所以转换是能成功的。
接口转换成实现类(向下转型)
package charactor;
public class Hero {
public String name;
protected float hp;
public static void main(String[] args) {
ADHero ad = new ADHero();
AD adi = ad;
ADHero adHero = (ADHero) adi;
ADAPHero adapHero = (ADAPHero) adi;
adapHero.magicAttack();
}
}
10行: ad引用指向ADHero, 而adi引用是接口类型:AD,实现类转换为接口,是向上转型,所以无需强制转换,并且一定能成功
12行: adi实际上是指向一个ADHero的,所以能够转换成功
14行: adi引用所指向的对象是一个ADHero,要转换为ADAPHero就会失败。
假设能够转换成功,那么就可以使用magicAttack方法,而adi引用所指向的对象ADHero是没有magicAttack方法的。
instanceof
instanceof Hero 判断一个引用所指向的对象,是否是Hero类型,或者Hero的子类
package charactor;
public class Hero {
public String name;
protected float hp;
public static void main(String[] args) {
ADHero ad = new ADHero();
APHero ap = new APHero();
Hero h1= ad;
Hero h2= ap;
//判断引用h1指向的对象,是否是ADHero类型
System.out.println(h1 instanceof ADHero);
//判断引用h2指向的对象,是否是APHero类型
System.out.println(h2 instanceof APHero);
//判断引用h1指向的对象,是否是Hero的子类型
System.out.println(h1 instanceof Hero);
}
}
重写
子类可以继承父类的对象方法
在继承后,重复提供该方法,就叫做方法的重写
又叫覆盖 override
父类Item有一个方法,叫做effect
package property;
public class Item {
String name;
int price;
public void buy(){
System.out.println("购买");
}
public void effect() {
System.out.println("物品使用后,可以有效果");
}
}
子类LifePotion继承Item,同时也提供了方法effect
package property;
public class LifePotion extends Item{
public void effect(){
System.out.println("血瓶使用后,可以回血");
}
}
调用就会执行重写的方法,而不是从父类的方法
所以LifePotion的effect会打印: “血瓶使用后,可以回血”
package property;
public class Item {
String name;
int price;
public void effect(){
System.out.println("物品使用后,可以有效果");
}
public static void main(String[] args) {
Item i = new Item();
i.effect(); //打印"物品使用后,可以有效果"
LifePotion lp =new LifePotion();
lp.effect(); //打印"血瓶使用后,可以回血"
}
}
多态
操作符的多态
同一个操作符在不同情境下,具备不同的作用 如果+号两侧都是整型,那么+代表 数字相加
如果+号两侧,任意一个是字符串,那么+代表字符串连接
类的多态现象
package property;
public class Item {
String name;
int price;
public void buy(){
System.out.println("购买");
}
public void effect() {
System.out.println("物品使用后,可以有效果 ");
}
public static void main(String[] args) {
Item i1= new LifePotion();
Item i2 = new MagicPotion();
System.out.print("i1 是Item类型,执行effect打印:");
i1.effect();
System.out.print("i2也是Item类型,执行effect打印:");
i2.effect();
}
}
-
i1和i2都是Item类型
-
都调用effect方法
-
输出不同的结果
多态: 都是同一个类型,调用同一个方法,却能呈现不同的状态
类的多态条件
要实现类的多态,需要如下条件
-
父类(接口)引用指向子类对象
-
调用的方法有重写
那么多态有什么作用呢? 通过比较不使用多态与使用多态来进一步了解
类的多态-不使用多态
假设英雄要使用血瓶和魔瓶,就需要为Hero设计两个方法 useLifePotion
useMagicPotion
除了血瓶和魔瓶还有很多种物品,那么就需要设计很多很多个方法,比如
usePurityPotion 净化药水
useGuard 守卫
useInvisiblePotion 使用隐形药水
等等等等
类的多态-使用多态
如果物品的种类特别多,那么就需要设计很多的方法
比如useArmor,useWeapon等等
这个时候采用多态来解决这个问题
设计一个方法叫做useItem,其参数类型是Item
如果是使用血瓶,调用该方法
如果是使用魔瓶,还是调用该方法
无论英雄要使用什么样的物品,只需要一个方法 即可
以上为how2j网站的部分内容,以下笔记来自b站黑马教程
ArratList
创建
ArrayList<要存储元素的数据类型> 变量名 = new ArrayList<>();
方法
-
boolean add(Object obj) 将指定元素obj追加到集合的末尾
-
Object get(int index) 返回集合中指定位置上的元素
-
int size() 返回集合中的元素个数
-
Object remove(int index) 从集合中删除指定index处的元素,返回该元素
-
boolean add(int index, Object obj) 将指定元素obj插入到集合中指定的位置
-
void clear() 清空集合中所有元素
-
Object set(int index, Object obj) 用指定元素obj替代集合中指定位置上的元素
一个标准的类
一个标准类通常包含四个组成
- 所有成员变量都用private修饰
- 为每一个成员变量写一对Getter/Setter方法
- 写一个无参数的构造方法
- 写一个全参数的构造方法
字符串
特点
- 字符串是常量,一旦被创建就不能改变 ,这是因为字符串的值是存放在方法区的常量池里面,但是引用可以改变。
- 字符串字面值"ab"也可以看成是一个字符串对象。
方法
比较方法
-
public boolean equals(Object obj): 比较字符串的内容是否相同,区分大小写
-
public boolean equalsIgnoreCase(String str): 比较字符串的内容是否相同,忽略大小写
-
public int compareTo(String str) 会对照ASCII 码表 从第一个字母进行减法运算 返回的就是这个减法的结果,如果前面几个字母一样会根据两个字符串的长度进行减法运算返回的就是这个减法的结果,如果连个字符串一摸一样 返回的就是0
-
public int compareToIgnoreCase(String str) 跟上面一样 只是忽略大小写的比较
注:
推荐写法:
String str = "abc";
boolean flag = "abc".equals(str);
不推荐写法:
String str = "abc";
boolean flag = "str".equals("abc");
因为str可以为null,如果使用不推荐写法,会出现空指针异常
获取方法
- public int length(): 获取字符串的长度。
- public char charAt(int index): 获取指定索引位置的字符
- public int indexOf(String str): 返回指定字符串在此字符串中第一次出现处的索引。(可以是单个字符)
- public String concat(String str): 把字符串拼接 。
- public String replace(String old,String new) 将指定字符串进行互换 。
截取方法
- public String substring(int start): 从指定位置开始截取字符串,默认到末尾。
- public String substring(int start,int end): 从指定位置开始到指定位置结束截取字符串。
转换方法
- public byte[] getBytes(): 把字符串转换为字节数组。
- public char[] toCharArray(): 把字符串转换为字符数组。
- public static String valueOf(char[] chs): 把字符数组转成字符串。(String类的valueOf方法可以把任意类型的数据转成字符串。)
- public String toLowerCase(): 把字符串转成小写。
- public String toUpperCase(): 把字符串转成大写。
分割方法
- public String[] split(String regex) :把字符串按照指定字符串分割
注:特殊字符作为分隔符时需要使用\进行转义.$|()[{^?*+\\
Static
补充:静态代码块
格式:
public class 类名称{
static{
//静态代码块的内容
}
}
特点:
-
当第一次用到本类时,静态代码块执行唯一的一次。
-
静态代码块比构造方法先执行,因为静态内容总是优先于非静态内容。
Arrays
- public static String toString(数组) // 将数组转化为字符串(按照默认格式)
int[] intArray = {10, 20, 30};
String intStr = Arrays.toString(intArray);
System.out.println(intStr); //输出结果为[10, 20, 30]
- void sort(数组) 对数组排序(默认从小到大排序)
- copyOf(int[] a, int newlength) 复制数组
- void fill(int[] a, int val) 初始化数组
- int binarySearch(int[] a,int key) 二分查找元素key所在下标
- boolean equals(array1,array2) 比较两个数组是否相等
注: 如果数组是自定义类型,那么这个自定义的类型要有Comparable或者Comparator接口的支持。
Math
- public static double abs(double num) 取绝对值
- public static double ceil(double num) 向上取整
- public static double floor(double num) 向下取整
- public static long round(double num) 四舍五入
- Math.PI代表近似的圆周率常数(double)
继承
###在父子类的继承关系中,如果成员变量重名,则创建子类对象时,访问有两种方式:
直接通过子类对象访问成员变量:等号左边是谁,就优先用谁,没有则向上找。
间接通过成员方法访问成员变量:该方法属于谁,就优先用谁,没有则向上找。
区分子类方法中重名的三种变量
局部变量:直接写成员变量名
本类的成员变量:this.成员变量名
父类的成员变量名:super.成员变量名
public class Zi extend Fu{
int num = 20;
public void method{
int num = 30;
System.out.println(num); //30,局部变量
System.out.println(this.num); //20,本类的成员变量
System.out.println(super.num); //父类的成员变量
}
}
重写注意事项
####重写安全检测
可以在重写前加一个@Override
如果不是重写,那么会报错
子类的方法的返回值必须小于等于父类的返回值范围
java.lang.Object类时所有类的公共最高父类(祖宗类)
子类方法的权限必须大于等于父类方法的权限修饰符
public > protected > (defalut) > private
注:default什么都不写,留空
父子类构造方法的访问特点
-
子类构造方法中有一个默认隐含的"super()"调用,所以一定是先调用的父类构造,后执行的子类构造。
-
子类构造可以通过super关键字来调用父类重载构造。
-
super的父类构造调用,必须是子类构造方法的第一个语句。所以一个子类构造不能调用多次super构造。
###super关键字的三种用法总结 -
在子类的成员方法中,访问父类的成员变量。
-
在子类的成员方法中,访问父类的成员方法。
-
在子类的构造方法中,访问父类的构造方法。
this关键字的三种用法总结
- 在本类的成员方法中,访问本类的成员变量。
- 在本类的成员方法中,访问本类的另一个成员方法。
- 在本类的构造方法中,访问本来的另一个构造方法。
注:在第三种用法中,this也必须是构造方法的第一个语句。所以,super和this不能同时使用。
Java继承的三个特点
- Java是单继承的,一个类的直接父类只能有唯一一个。
- Java可以多级继承。
- 一个子类的直接父亲是唯一的,但是一个父亲可以拥有很多个子类。
抽象
抽象方法和抽象类的格式
抽象方法:就是加上abstract关键字,然后去掉大括号,直接分号结束。
抽象类:抽象方法所在的类,必须是抽象类才行。在class之前写上abstract即可。
public abstract class Animal{
public abstract void eat(); //抽象方法
public void normalMethod(){ //普通方法
}
}
如果使用抽象类和抽象方法
- 不能直接创建new抽象类对象。
- 必须用一个子类来继承抽象父类。
- 子类必须覆盖重写抽象父类中所有的抽象方法。
- 创建子类对象进行使用。
public class Cat extends Animal{
@Override
public void eat{
System.out.println("猫吃鱼");
}
}
注意事项
- 抽象类不能创建对象,只能创建非抽象子类的对象。
- 抽象类中可以有构造方法,是供子类创建对象时,初始化父类成员使用的。
- 抽象类中不一定包含抽象方法,但是抽象方法的类必定是抽象类。
- 抽象类的子类,必须重写抽象父类中所有的抽象方法。
接口
接口就是一种公共的规范标准
接口定义的基本格式
public interface 接口名称{
//接口内容
}
如果是Java 7,那么接口中可以包含的内容有:
- 常量
- 抽象方法
如果是Java 8,还可以额外包含有:
- 默认方法
- 静态方法
如果是Java 9,还可以额外包含有:
- 私有方法
接口的抽象方法定义
- 接口当中的抽象方法,修饰符必须是两个固定的关键字:public abstract
- 这两个关键字修饰符,可以选择性地省略。
public interface MyInterfaceAbstract {
public abstract void methodAbs1(); //这是一个抽象方法
public void methodAbs2(); //这也是一个抽象方法(省略abstract)
abstract void methodAbs3(); //这也是一个抽象方法(省略public)
void methodAbs4(); //这也是一个抽象方法(省略public和abstract)
}
###接口的抽象方法使用
-
接口不能直接使用,必须有一个“实现类”来实现该接口
public class 实现类名称 implements 接口名称{
}
-
接口的实现类必须覆盖重写接口中所有的抽象方法。
-
创建实现类的对象,进行使用。
public class MyInterfaAbstractImpl implements MyInterfaceAbstract {
@Override
public void methodAbs1() {
System.out.println("这是第一个方法");
}
@Override
public void methodAbs2() {
System.out.println("这是第二个方法");
}
@Override
public void methodAbs3() {
System.out.println("这是第三个方法");
}
@Override
public void methodAbs4() {
System.out.println("这是第四个方法");
}
}
接口的默认方法定义
默认方法可以解决接口升级的问题
格式:
public default 返回值类型 方法名称(参数列表){
方法体
}
接口的默认方法使用
public interface MyInterfaceDefault{
public abstract void methodAbs1(); //这是一个抽象方法
public default void methodAbs2(){
System.out.println("这是一个默认方法");
}
}
- 调用默认方法,如果实现类当中没有,会向上找接口。
- 接口的默认方法,可以通过接口实现类直接调用,也可以进行覆盖重写。
接口的静态方法定义
格式:
public static 返回值类型 方法名称(参数列表){
方法体
}
###接口的静态方法使用
不能通过接口实现类的对象来调用接口当中的静态方法。
正确用法:通过接口名称直接调用其中的静态方法。
public interface MyInterfaceStatic {
public static void methodStatic(){
System.out.println("这是一个静态方法");
}
}
public class MyInterfaceStaticImpl implements MyInterfaceStatic {
}
public class DemoInterfaceStatic {
public static void main(String[] args) {
MyInterfaceStaticImpl impl = new MyInterfaceStaticImpl(); //其实也没必要,因为不需要对象
// 错误写法:impl.methodStatic();
MyInterfaceStatic.methodStatic(); //正确写法
}
}
接口的私有方法定义
如果我们需要抽取一个共有方法,用来解决两个默认方法之间重复代码的问题。
但是这个共有方法不能让实现类使用,应该是私有化的。
-
普通私有方法,解决多个默认方法之间重复代码的问题。
格式:
private 返回值类型 方法名称(参数列表){
方法体
}
-
静态私有方法,解决多个静态方法之间重复代码的问题。
格式:
private static 返回值类型 方法名称(参数列表){
方法体
}
接口的私有方法使用
public interface MyInterfacePrivate{
public default void method1(){
System.out.println("默认方法1");
methodCommon();
}
public default void method2(){
System.out.println("默认方法2");
methodCommon();
}
private default void methodCommon(){
System.out.println("111");
System.out.println("222");
System.out.println("333");
}
}
接口的常量定义和使用
- 接口当中也可以定义“成员变量”,但是必须使用public static final三个关键字修饰(可以省略),从效果上看其实就是常量,不可以改变。
- 接口中的常量,必须进行赋值。
- 接口中常量的名称,使用大写字母,可以用_来分隔。
- 常量使用时,直接通过接口名称.常量进行使用。
public interface MyInterfaceConst{
//这其实是一个常量,一旦复制,不可以修改
public static final int num = 10;
}
继承父类并实现多个接口
- 接口是没有静态代码块和构造方法的。
- 一个类的直接父类是唯一的,但是一个类可以同时实现多个接口。
- 如果实现类所实现的多个接口中,存在重复的抽象方法,那么只需要覆盖重写一次即可。
- 如果实现类没有覆盖重写所有接口当中的所有抽象方法,那么实现类就必须是一个抽象类。
- 如果实现类所实现的多个接口当中,存在重复的默认方法,那么实现类一定要对冲突的默认方法进行覆盖重写。
- 如果一个类的直接父类当中的方法和接口当中的默认方法发生了冲突,优先用父类当中的方法。
多态
- 基类引用指向派生类对象,称作赋值兼容(向上转型)。
- 可以调用基类属性和方法,不能调用派生类新增的属性和方法。
- 基类引用可调用派生类重写的基类方法。
多态的格式与使用
父类名称 对象名 = new 子类名称( );
或者
接口名称 对象名 = new 实现类名称( );
多态中成员变量的使用特点
直接通过子类对象访问成员变量:等号左边是谁,就优先用谁,没有则向上找。
间接通过成员方法访问成员变量:该方法属于谁,就优先用谁,没有则向上找。
多态中成员方法的使用特点
看new的是谁,就优先用谁,没有则向上找。
口诀:
成员变量:编译看左边,运行还看左边。
成员方法:编译看左边,运行看右边。
多态的好处
无论右边new的时候换成哪个子类对象,等号左边调用方法都不会变化。
对象的转型
向上转型
对象的向上转型,其实就是多态的写法:
格式:父类名称 对象名 = new 子类名称( );
含义:右侧创建一个子类对象,把它当作父类来看待使用。
向上转型一定是安全的
向下转型
对象的向下转型,其实是一个还原的动作:
格式:子类名称 对象名 = (子类名称)父类对象;
含义:将父类对象还原为本来的子类对象。
注意事项:必须保证对象本来创建的时候,是由将转类型转换来的,否则会报错。
instanceof
如何才能知道一个父类的引用对象本来是什么子类?
格式:
对象 instanceof 类型
会得到一个boolean结果
所以向下转型的时候一定要用instanceof关键字结合if语句,避免错误。
final
final修饰类
格式:
public final class 类名称{
}
含义:这个类不能有任何的子类(太监类)
注意:一个类如果被final修饰,那么其中所有的成员方法都无法进行覆盖重写(因为没有子类)。
###final修饰成员方法
格式:
修饰符 fianl 返回值类型 方法名称(参数列表){
}
含义:这个方法不能被覆盖重写。
注意:对于类和方法来说,abstract关键字和final关键不能同时使用,因为矛盾。
final修饰局部变量
局部变量被final修饰,那么这个变量不能进行更改。
注意:
对于基本类型来说,不可变说的是变量当中的数据不可改变。
对于引用类型来说,不可变说的是变量当中的地址值不可改变,内容可变。
final修饰成员变量
成员变量被final修饰,那么这个变量不能进行更改。
- 由于成员变量具有默认值,所以用了final之后必须手动赋值,不会再给默认值了。
- 对于final的成员变量,要么使用直接赋值,要么通过构造方法赋值。
权限修饰符
Java有四种权限修饰符
public > protected > (default) > private
public | protected | (default) | private | |
---|---|---|---|---|
同一个类 | √ | √ | √ | √ |
同一个包 | √ | √ | √ | × |
不同包子类 | √ | √ | × | × |
不同包非子类 | √ | × | × | × |
内部类
内部类的概念及分类
概念:如果一个事物的内部包含另一个事物,那么这就是一个类内部包含另一个类。
分类:1.成员内部类 2.局部内部类(包含匿名内部类)
成员内部类的定义
修饰符 class 类名称{
修饰符 class 内部类名称{
}
}
注意:内用外,随意访问;外用内,需要内部类对象。
成员内部类的使用
- 间接方法:在外部类的方法中使用内部类,然后在main中只调用外部类的方法。
- 直接方式:外部类名称.内部类名称 对象名 = new 外部类名称().new 内部类名称();
内部类的同名变量访问
public class Outer{
int num = 10;
public class Inner{
int num = 20;
public void methodInner(){
int num = 30;
System.out.println(num); //30
System.out.println(this.num); //20
System.out.println(Outer.this.num) //10
}
}
}
局部内部类的定义
如果一个类是定义在一个方法内部的,那么这就是一个局部内部类。
局部:只有当前所属的方法才能使用他,出了这个方法外面就不能用了。
格式:
修饰符 class 外部类名称{
修饰符 返回值类型 外部类方法名称(参数列表){
class 局部内部类名称{
}
}
}
注意:定义一个类的时候,权限修饰符规则
- 外部类:public / (default)
- 成员内部类:public / protected / (default) / private
- 局部内部类:什么都不能写
局部内部类,如果希望访问所在方法的局部变量,那么这个局部变量必须是有效final的,即只能赋值一次。
匿名内部类
如果接口的实现类(或者父类的子类)只需要使用唯一的一次,那么这种情况下就可以省略掉该类的定义,而改为使用匿名内部类。
格式:
接口名称 对象名 = new 接口名称(){
//覆盖重写所有抽象方法
};
Object类
java.lang.Object类是最顶层的类,是所有类的父类
###toString方法
所有的类继承了Object类,所以可以使用Object类中的toString方法
String toString()返回该对象的字符串表示
看一个类是否重写了toString方法,直接打印这个类对应对象的名字即可:
如果没有重写,那么打印的就是对象的地址值。
如果重写了,那么就按照重写的方式打印。
equals方法
boolean equals(Object obj)指示其他某个对象是否与此对象“相等”,默认比较的是地址(没有意义),所以需要重写。
Objects类方法
equals方法可以比较两个对象是否相同,相比于之前的方法更完善。
Date类
毫秒值的概念和作用
- java.util.Date类表示日期和时间类,精确到毫秒
- System.currentTimeMillis( )获取当前系统一共到时间原点(1970年1月1日00:00:00)经历了多少毫秒
- 毫秒转换为日期:1天 = 86400秒 = 86400000毫秒
- 中国是东八区,会把时间增加8个小时
Date类的构造方法和成员方法
####构造方法
-
Date( )类的无参构造获取的就是当前系统的日期和时间。
-
Date( )类的带参构造方法:Date(long date),传递毫秒值,把毫秒转换为Date日期。
成员方法
long getTime( ), 把日期转换为毫秒,相当于(System.cunrrentTimeMillis( ))
String toLocaleString( )根据本地格式转换日期对象
DateFormat类
作用:日期转化为文本,文本解析为日期
成员方法
String format(Date date) 日期转为文本
Date parse(String source) 文本转为日期
DateFormat是一个抽象类,不能直接创建对象,可以使用它的子类SimpleDateFormat
SimpleDateFormat
构造方法
SimpleDateFormat(String pattern) 用给定的模式和默认语言环境的日期格式符号构造
参数:传递指定的模式
模式:区分大小写
y 年
M 月
d 日
H 时
m 分
s 秒
package pack1;
import java.lang.reflect.Array;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.Scanner;
/**
* @author 86156
*/
public class Demo01 {
public static void main(String[] args) throws ParseException {
SimpleDateFormat date = new SimpleDateFormat("yyyy年MM月dd日HH时mm分ss秒");
Date d = new Date();
String s = date.format(d);
System.out.println(s);
d = date.parse("2050年10月09日19时33分41秒");
System.out.println(d);
}
}
Calendar类(日历类)
Calendar类是一个抽象类,无法直接创建对象,里面有一个静态方法叫getInstace( ),返回Calendar类的子类对象
常用成员方法
-
public int get(int field):返回给定日历字段的值
1 年
2 月
5 月中的某一天
10 时
12分
13 秒
-
public int set(int field, int value):讲给定的日历字段设置为定值
同时设置年月日可以使用set的重载方法,直接传入3个参数,为要改变的年月日
-
public abstract void add(int field, int amount):根据日历规则,为给定的日历字段添加或减去指定的时间量
-
public Date getTime( ):返回表示此时的Date对象
System类
public static native void arraycopy(Object src, int srcPos, Object dest, int destPos, int length) 将原数组的指定元素复制到目标数组
public class Demo01 {
public static void main(String[] args) throws ParseException {
int[] a = {1, 2, 3, 4, 5};
int[] b = {6, 7, 8, 9, 10};
System.arraycopy(a, 0, b, 0, 3);
System.out.println(Arrays.toString(b)); //[1, 2, 3, 9, 10]
}
}
StringBuilder类
String类中的字符串是常量,创建之后不能更改,底层是一个被final修饰的数组,不能改变,所以进行相加的时候内存会有多个字符串,占用空间多,效率低下
StringBuilder类为字符串缓冲区,可以提高字符串的操作效率(看成一个长度可以改变的字符串),底层是一个数组,占用空间少,效率高
构造方法
- public StringBuilder( ):构造一个空的StringBuilder容器
- public StringBuilder(String str):构造一个StringBuilder容器,并将字符串添加进去
成员方法
-
public StringBuilder append( …):添加任意类型数据的字符串形式,并返回当前对象自身
无需接受返回值
-
public String toString( ):将当前StringBuilder对象转换为String对象
-
public StringBuilder reverse( ):反转内容
包装类
将基本数据类型包装到一个类里,可以像操作对象一样操作基本数据类型
装箱
基本数据类型----->包装类
-
构造方法
Integer(int value)
构造一个新分配的 Integer对象,该对象表示指定的 int值。
Integer(String s)
构造一个新分配 Integer对象,表示 int由指示值 String参数。 -
静态方法
static Integer valueOf(int i)
返回一个 Integer指定的 int值的 Integer实例。
static Integer valueOf(String s)
返回一个 Integer对象,保存指定的值为 String 。
拆箱
包装类----->基本数据类型
成员方法:intValue() 将 Integer的值作为 int 。
自动装箱与自动拆箱
基本数据类型和包装类可以自动的相互转换
基本数据类型和字符串类型的相互转换
基本类型---->字符串
- 基本类型数据的值+"" (最简单,工作中常用)
- 包装类的静态方法:static String toString(int i)
- String类的静态方法static String valueOf(int i)
字符串---->基本类型
使用包装类的静态方法parseXxx(String s)(除了char类都可以)
Collection集合
集合与数组的区别
- 集合是一种容器,可以储存多个数据
- 数组长度固定,集合长度可变
- 数组储存的是同一类型的元素,集合储存的是对象,类型可以不一致
集合框架
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cZlkrBYv-1610859583836)(C:\Users\86156\AppData\Local\Temp\1602413755440.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5Jse1rBs-1610859583838)(C:\Users\86156\AppData\Local\Temp\1602414821373.png)]
Collection常用功能
- public boolean add(E e):把给定对象添加到当前集合中
- public void clear( ):清空集合所有元素
- public boolean remove(E e):把给定对象在当前集合中删除
- public boolean contains(E e):判断当前集合中是否包含给定的对象
- public boolean isEmpty( ):判断当前集合是否为空
- public int size( ):返回集合中元素的个数
- public Object[ ] toArray( ):把集合中的元素储存到数组中
Iterator迭代器
Iterator接口
两个常用方法
- boolean hasNext( ):判断集合中是否还有元素可以迭代
- E next( ):返回迭代的下一个元素
Iterator迭代器是一个接口,无法直接使用,需要用Iterator接口的实现类对象。Collection接口中有一个方法叫iterator( ),返回的就是迭代器的实现类对象
迭代器的使用步骤:
- 使用iterator( )获取迭代器的实现类对象
- 使用hasNext判断还有没有下一个元素
- 使用next取出下一个元素
迭代器的代码实现
package pack1;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
/**
* @author 86156
*/
public class Student1{
public static void main(String[] args) {
Collection<String> coll = new ArrayList<>();
coll.add("姚明");
coll.add("科比");
coll.add("麦迪");
coll.add("科比");
coll.add("艾弗森");
Iterator<String> it = coll.iterator();
while (it.hasNext()){
String s = it.next();
System.out.println(s);
}
System.out.println("---------------------");
for (Iterator<String> it2 = coll.iterator(); it2.hasNext();){
String s = it2.next();
System.out.println(s);
}
}
}
增强for循环
专门用来遍历数组和集合,内部原理其实是一个迭代器,简化了迭代器的书写,所以遍历过程中,不能进行增删操作
格式:for(集合/数组的数据类型 变量名:集合名/数组名){
sout(变量名);
}
package pack1;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
/**
* @author 86156
*/
public class Student1{
public static void main(String[] args) {
Collection<String> coll = new ArrayList<>();
coll.add("姚明");
coll.add("科比");
coll.add("麦迪");
coll.add("科比");
coll.add("艾弗森");
for (String i:coll){
System.out.println(i);
}
int[] a = {1, 2, 3, 4, 5, 6};
for(int i:a){
System.out.println(i);
}
}
}
泛型
泛型的概念
泛型是一种未知的数据类型,不知道使用什么数据类型时可以使用泛型
E e:Element 元素
T t:Type 类型
使用泛型
好处:
- 避免了类型转换的麻烦,存储的是什么类型,取出的就是什么类型
- 把运行期异常(代码运行之后会抛出的异常)提升到了编译期(写代码的时候会报错)
弊端:泛型是什么类型,只能存储什么类型的数据
不使用泛型
好处:默认Object类型,可以存储任意类型的数据
弊端:不安全,会引发异常
定义和使用含有泛型的类
权限修饰符 class 类名<代表泛型的变量> { }
package pack1;
public class Generic<E>{
private E name;
public E getName() {
return name;
}
public void setName(E name) {
this.name = name;
}
}
package pack1;
public class Demo01 {
public static void main(String[] args) {
Generic g1 = new Generic();
g1.setName("123");
System.out.println(g1.getName());
Generic<Integer> g2 = new Generic<>();
g2.setName(123);
System.out.println(g2.getName());
Generic<String> g3 = new Generic<>();
g3.setName("123");
System.out.println(g3.getName());
}
}
定义和使用含有泛型的方法
格式:修饰符 <泛型> 返回值类型 方法名(参数列表(使用泛型)){ }
含有泛型的方法,在调用方法的时候确定泛型的数据类型,传递什么类型的参数,泛型就是什么类型
package pack1;
public class Generic<E>{
public <M> void method1(M m){
System.out.println(m);
}
}
package pack1;
public class Demo01 {
public static void main(String[] args) {
Generic g = new Generic();
g.method1(123);
g.method1(1.1);
g.method1("123");
g.method1(true);
}
}
定义和使用含有泛型的接口
格式:public interface 接口名<泛型>{ }
使用方法
-
定义接口的实现类,实现接口,指定接口的泛型
package pack1; /** * @Author: 2019012364 袁梦达 3班 */ public interface GenericInterface<I> { public abstract void method(I i); } package pack1; /** * @Author: 2019012364 袁梦达 3班 */ public class GenericInterfaceImpl implements GenericInterface<String>{ @Override public void method(String s) { System.out.println(s); } } package pack1; public class Demo01 { public static void main(String[] args) { GenericInterfaceImpl g1 = new GenericInterfaceImpl(); g1.method("123"); } }
-
接口使用什么泛型,实现类就使用什么泛型,类跟着接口走,相当于定义了一个含有泛型的类,创建对象的时候确定泛型的类型
package pack1; /** * @Author: 2019012364 袁梦达 3班 */ public class GenericInterfaceImpl<I> implements GenericInterface<I>{ @Override public void method(I i) { System.out.println(i); } } package pack1; public class Demo01 { public static void main(String[] args) { GenericInterfaceImpl<Integer> g1 = new GenericInterfaceImpl(); g1.method(123); GenericInterfaceImpl<String> g2 = new GenericInterfaceImpl<>(); g2.method("123"); } }
泛型通配符
当使用泛型类或者接口时,传递的数据中泛型类型不确定,可以通过通配符<?>表示,但是一旦使用泛型的通配符后,只能使用Object类中的共性方法,集合中元素自身方法无法使用。
不能创建对象使用,只能作为方法的参数
package pack1;
import java.util.ArrayList;
import java.util.Iterator;
public class Demo01 {
public static void main(String[] args) {
ArrayList<Integer> list1 = new ArrayList<>();
list1.add(1);
list1.add(2);
ArrayList<String> list2 = new ArrayList<>();
list2.add("张三");
list2.add("李四");
//定义一个方法变量集合(不确定类型)
printArray(list1);
printArray(list2);
}
public static void printArray(ArrayList<?> list){
Iterator<?> iterator = list.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
}
}
为什么不能使用Object,因为泛型没有继承的概念
List集合
特点
- 有序的集合,存储元素和取出元素的顺序是一致的
- 有索引,包含了一些带索引的方法
- 允许存储重复的元素
List带索引的方法
- public void add(int index, E element):将指定的元素添加到该集合的指定位置上。
- public E get(int index):返回集合中指定位置的元素。
- public E remove(int index):移除列表中指定位置的元素,返回的是被移除的元素。
- public E set(int index, E element):用指定元素替换集合中指定位置的元素,返回值是更新前的元素。
ArrayList集合
查找快,增删慢,最常用的集合
LinkedList集合
存储结构是链表结构,查询慢,增删快
包含了大量操作首尾元素的方法
使用LinkedList特有方法时不能使用多态
-
public void addFirst(E e):将指定元素插入此列表的开头。
-
public void addLast(E e):将指定元素添加到此列表的结尾,等效于add。
-
public void push(E e):将此元素推入此列表所表示的堆栈,等效于addFirst
-
public E getFirst( ):返回此列表的第一个元素
-
public E getLast( ):返回此列表的最后一个元素
-
public E removeFirst( ):移除并返回此列表的第一个元素
-
public E removeLast( ):移除并返回此列表的最后一个元素
-
public E pop( ):从此列表所表示的堆栈处弹出一个元素,相当于removeFirst
-
public boolean isEmpty( ):如果列表不包含元素,则返回true
Set集合
特点
- 不包含重复元素
- 没有索引,不能使用普通的for循环
HashSet集合
是一个无序的集合,存储元素和取出元素的顺序有可能不一致。
底层是一个哈希表(数组+链表 / 数组 + 红黑树)结构,查询的速度非常的快。
使用HashSet集合存储自定义类型元素必须重写hashCode和equals方法
LinkedHashSet集合
底层是一个哈希表(数组+链表 / 数组 + 红黑树)+链表:多了一条链表(记录元素的存储顺序),保证元素有序。
可变参数
- 使用前提:当方法的参数列表数据类型已经确定,但是参数的个数不确定,就可以使用可变参数。
- 使用格式:修饰符 返回值类型 方法名(数据类型…变量名){ }
- 原理:底层是一个数组,根据传递参数个数不同,来存储这些参数
- 注意:
- 一个方法的参数列表,只能有一个可变参数
- 如果方法的参数有多个,那么可变参数必须写在参数列表额末尾。
- 可变参数的特殊(终极)写法:public void method(Object…object)
Collections集合工具类
方法
-
public static boolean addAll(Collections c, T…elements):往集合中添加一些元素
-
public static void shuffle(List<?> list) 打乱集合顺序
-
public static void sort(List list):将集合中元素按照默认规则排序
sort使用前提:被排序的集合里边存储的元素必须实现Comparable,重写接口中的方法compareTo定义排序的规则:this - 参数 为升序排序
参数 - this 为降序排序
-
public static void sort(List list, Comparator<? super T>):将集合中元素按照指定规则排序
-
Comparator和Comparable的区别
Comparable:自己(this)和别人(参数)比较,需要自己实现Comparable接口,重写比较的规则compareTo方法
Comparator:相当于找一个第三方的裁判进行比较
Map集合
Collection是单列集合,Map是双列几何
Map<k, v>特点
- 不能包含重复的k(key)键,value可以重复,每个键只能对应一个值v(value)
- key和value的数据类型可以相同,可以不同
Map集合常用子类
HashMap集合特点:
- 底层是哈希表,查询速度特别快
- HashMap是一个无序集合
LinkedHashMap集合特点:
- 底层是哈希表+链表
- LinkedHashMap是一个有序的集合
Map接口的常用方法
-
public V put(K key, V value):把指定的键与指定的值添加到Map集合中
返回值:v
存储键值对的时候,key不重复,返回值v是null;重复时,会使用新的value替换map中的重复value,返回被替换的value
-
public V remove (Object key):把指定的键所对应的键值对元素在Map中删除,返回被删除元素的值
返回值:v
key存在,返回被删除的值,不存在返回null
-
public V get(Object key):根据指定的键,在Map集合中获取对应的值
返回值:v
key存在,返回对应的值,不存在返回null
-
public boolean containsKey(Object key):判断集合中是否包含指定的键
包含返回true,不包含返回false
Map集合遍历键找值
第一种
通过键找值的方式
- 使用Set keySet( )方法,把Map集合所有的key取出来,存储到一个Set集合中
- 遍历Set集合,获取Map集合中的每一个key
- 通过Map集合中的get(key),通过key找到value
第二种
使用Entry对象遍历
Map集合中的方法:Set<Map.Entry<K, V>> entrySet( )返回此映射中包含的映射关系的Set视图
实现步骤:
- 使用Map集合中的entrySet( ),把Map集合中多个Entry对象取出来,存储到一个Set集合中
- 遍历Set集合,获取每一个Entry对象
- 使用Entry对象中的方法getKey( )和getValue( )获取键与值
HashMap存储自定义类型键值
需要重写HashCode( )和Equals( )方法
LinkedHashMap
key不允许重复,有序集合
HashTable集合
底层也是一个哈希表,是一个线程安全的集合,是单线程集合,速度慢,不可以存储null值,null键
和Vector集合一样,在jbk1.2版本之后被取代了
但HashTable的子类Properties依然在使用
JDK9的of方法
static List of(E…element)可以给集合一次性添加多个元素
使用前提:当集合中存储的元素的个数已经确定了,不再改变时使用
注意:
- of方法只适用于List接口,Set接口,Map接口,不适用于接口的实现类
- of方法的返回值是一个不能改变的集合,集合不能再使用add,put方法
- Set接口和Map接口在调用of方法时不能有重复的元素
异常
异常概念
指的是程序在执行过程中,出现的非正常的情况,最终会导致JVM的非正常停止。
异常体系
异常的根类是Throwable,有两个子类,一个是Error,一个是Exception。
Error:严重错误,无法通过处理的错误,只能事先避免,好比绝症。
Exception:表示异常,异常产生后程序员可以通过代码的方式纠正,使程序继续运行,是必须要处理的,好比感冒,阑尾炎。
异常分类
Exception:编译期异常,进行编译(写代码)java程序出现的问题。
RuntimeException:运行期异常,java程序运行过程中出现的问题。
异常就相当于程序得了一个小毛病(感冒),把异常处理掉,程序可以继续执行(吃点药,继续工作)
Error:错误,相当于程序得了绝症,必须修改源代码,程序才能继续执行
throw关键字
作用:在指定的方法中抛出指定的异常
使用格式:throw new xxxException(“异常产生的原因”);
注意:
-
throw必须写在方法的内部
-
throw后边new的对象必须是Exception或者Exception的子类对象
-
throw抛出指定的异常对象,我们就必须处理这个异常对象
throw后面创建的是RuntimeException或者是RuntimeException的子类对象,我们可以不处理,默认交给JVM处理(打印异常对象,中断程序)
throw后面创建的是编译异常(写代码的时候报错),我们就必须处理这个异常,要么throws,要么try…catch
以后我们必须对方法传递过来的参数进行合法性校验,如果参数不合法,那么我们就必须使用抛出异常的方式,告知方法的调用者传递的参数有问题
package pack1;
public class Demo01 {
public static void main(String[] args) {
int[] arr1 = null;
int[] arr2 = {1, 2, 3};
int e1 = getElement(arr1, 0);
int e2 = getElement(arr2, 5);
System.out.println(e1);
System.out.println(e2);
}
private static int getElement(int[] arr, int index) {
if(arr == null){
throw new NullPointerException("传递的数组是null!!!");
}
if(index < 0 || index >= arr.length){
throw new ArrayIndexOutOfBoundsException("越界了!!!");
}
int e = arr[index];
return e;
}
}
requireNonNull方法
import java.util.Objects;
public class Demo01 {
public static void main(String[] args) {
method(null);
}
private static void method(Object obj) {
//对传递过来的参数进行合法性判断,判断是否为null
/*if(obj == null){
throw new NullPointerException("传递的对象的值是null");
}*/
//Objects.requireNonNull(obj);
Objects.requireNonNull(obj, "传递的对象为null!!!");
}
}
throws关键字
异常处理的第一种方式,交给别人处理
作用:当方法内部抛出异常时,我们就必须处理这个异常对象,可以使用throws关键字处理异常对象,会把异常对象声明抛出给方法的调用者处理(自己不处理,给别人处理),最终交给JVM处理–>中断处理
格式:在方法声明时使用
修饰符 返回值类型 方法名(参数列表) throws AAAException , BBBException…{
throw new AAAException(“产生原因”);
throw new BBBException(“产生原因”);
…
}
注意:
-
throws关键字必须写在方法声明处
-
throws关键字后边声明的异常必须是Exception或Exception的子类
-
方法内部如果抛出了多个异常对象,那么throws后边必须也声明多个异常
如果抛出的多个异常对象有父子类关系,那么直接声明父类异常即可
-
调用了一个声明抛出异常的方法,我们就必须处理声明的异常
要么继续使用throws声明抛出,交给方法的调用者处理,最终交给JVM,要么try…catch自己处理异常
package pack1;
import java.io.FileNotFoundException;
import java.util.Objects;
public class Demo01 {
public static void main(String[] args) throws FileNotFoundException{
//readFile("c:\\a.txt");
readFile("D:\\a.txt");
}
//定义一个方法,对传递的文件路径进行合法性判断
//如果路径不是c:\\a.txt,那么就抛出文件找不到异常对象,告知方法的调用者
public static void readFile(String filename) throws FileNotFoundException{
if(!filename.equals("c:\\a.txt")){
throw new FileNotFoundException("路径错误!!!");
}
System.out.println("路径没有问题!!!");
}
}
捕获异常try…catch
异常处理的第二种方式,自己处理异常
格式:
try{
可能产生异常的代码
}catch(定义一个异常的变量,用来接受try中抛出的异常对象 ){
异常的处理逻辑,产生异常对象之后,怎么处理异常对象
一般会把异常的信息记录到一个日志中
}
…
catch(异常类名 变量名){
}
注意:
-
try中可能会抛出多个异常对象,那么就可以使用多个catch来处理这些异常对象
-
如果try中产生了异常,那么就会执行catch中的异常处理逻辑,执行完毕catch中的处理逻辑,继续执行try…catch之后的代码
如果try中没有产生异常,那么就不会执行catch中异常的处理逻辑,执行完try中的代码,继续执行try…catch之后的代码
package pack1;
import java.io.FileNotFoundException;
import java.util.Objects;
public class Demo01 {
public static void main(String[] args) throws FileNotFoundException{
try{
//readFile("c:\\a.txt");
readFile("d:\\a.txt");
}catch (FileNotFoundException e){
System.out.println("路径错误了呜呜呜");
}
System.out.println("后续代码");
}
//定义一个方法,对传递的文件路径进行合法性判断
//如果路径不是c:\\a.txt,那么就抛出文件找不到异常对象,告知方法的调用者
public static void readFile(String filename) throws FileNotFoundException{
if(!filename.equals("c:\\a.txt")){
throw new FileNotFoundException("路径错误!!!");
}
System.out.println("路径没有问题!!!");
}
}
Throwable类中三个处理异常的方法
- String getMessage( )返回此throwable的简短描述
- String toString( )返回此throwable的详细信息字符串
- void printStackTrace( ) JVM打印异常对象,默认调用此方法,打印的异常信息是最全面的
finally代码块
格式:
try{
可能产生异常的代码
}catch(定义一个异常的变量,用来接受try中抛出的异常对象 ){
异常的处理逻辑,产生异常对象之后,怎么处理异常对象
一般会把异常的信息记录到一个日志中
}
…
catch(异常类名 变量名){
}finally{
无论是否出现异常都会执行
}
注意:
- finally不能单独使用,必须和try一起使用
- finally一般用于资源释放(资源回收),无论程序是否出现异常,最后都要资源释放(IO)
- finally里如果有return,将永远返回finally中return的结果,所以要避免使用return
package pack1;
import java.io.FileNotFoundException;
import java.util.Objects;
public class Demo01 {
public static void main(String[] args) {
try {
readFile("d:\\a.txt");
} catch (FileNotFoundException e) {
e.printStackTrace();
}finally {
System.out.println("资源释放!!");
}
}
//定义一个方法,对传递的文件路径进行合法性判断
//如果路径不是c:\\a.txt,那么就抛出文件找不到异常对象,告知方法的调用者
public static void readFile(String filename) throws FileNotFoundException{
if(!filename.equals("c:\\a.txt")){
throw new FileNotFoundException("路径错误!!!");
}
System.out.println("路径没有问题!!!");
}
}
多异常的捕获处理
多个异常使用捕获如何处理?
- 多个异常分别处理
- 多个异常一次捕获,多次处理
- 多个异常一次捕获一次处理
package pack1;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
public class Demo01 {
public static void main(String[] args) {
/*第一种,分别处理
try{
int[] arr = {1, 2, 3};
System.out.println(arr[4]);
}catch (ArrayIndexOutOfBoundsException e){
System.out.println(e);
}
try{
ArrayList<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
System.out.println(list.get(4));
}catch (IndexOutOfBoundsException e){
System.out.println(e);
}
*/
/*
第二种,一次捕获,多次处理
try{
int[] arr = {1, 2, 3};
//System.out.println(arr[4]);
ArrayList<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
System.out.println(list.get(4));
}catch (ArrayIndexOutOfBoundsException e){
System.out.println(e);
}catch (IndexOutOfBoundsException e){
System.out.println(e);
}
一个try多个catch注意事项:
catch里边定义的异常变量,如果有字符类关系,那么子类的异常变量必须写在上边,否则就会报错
*/
//第三种,一次捕获一次处理
try{
int[] arr = {1, 2, 3};
//System.out.println(arr[4]);
ArrayList<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
System.out.println(list.get(4));
} catch (Exception e){
System.out.println(e);
}
System.out.println("后续代码");
}
}
父子类异常
- 如果父类抛出了多个异常,子类重写父类方法时,抛出和父类相同的异常或者是父类异常的子类或者不抛出
- 父类方法没有抛出异常,子类重写父类该方法时也不可以抛出异常,此时子类产生异常,只能捕获处理,不能声明抛出
自定义异常
java提供的异常类不够我们使用,需要自己定义一些异常类
格式:public class XXXExcepiton extends Exception / RuntimeException{
添加一个无参构造
添加一个带异常信息的构造方法
}
注意:
-
自定义异常类一般都是以Exception结尾,说明该类是一个异常类
-
自定义异常类,必须继承Exception或者RuntimeExcption
继承Exception,那么自定义的异常类就是一个编译期异常,如果方法内部抛出了编译期异常,就必须处理这个异常,要么throws,要么try…catch
继承RuntimeException,那么自定义异常类就是一个运行期异常,无需处理,交给虚拟机处理(中断处理)
package pack1;
/**
* @Author: 2019012364 袁梦达 3班
*/
public class RegisterException extends Exception{
//无参构造
public RegisterException(){
super();
}
//带异常信息的构造方法,方法内部调用父类异常信息的构造方法,让父类来处理这个异常信息
public RegisterException(String message){
super(message);
}
}
多线程
并发与并行
并发:指两个或多个事件在同一个时间段内发生,交替执行,相当于一个人吃两个馒头
并行:指两个或多个事件在同一时刻发生(同时发生),相当于两个人吃两个馒头
线程与进程
进程:进入到内存的程序
线程:是进程中的一个执行单元,负责当前进程中程序的执行,一个进程至少有一个线程,也可以有多个线程
多线程好处:
- 效率高
- 多个线程之间互不影响
线程调度
- 分时调度:所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间
- 抢占式调度:优先让优先级高的进程使用CPU,如果优先级相同,那么会随机选择一个,java为抢占式调度
主线程
主线程:执行主(main)方法的线程
单线程程序:java程序中只有一个线程
执行从main方法开始,从上到下依次执行
创建线程类
第一种:创建Thread类的子类
实现步骤:
-
创建一个Thread类的子类
-
在Thread的子类中重写Thread类的run方法,设置线程任务(开启线程要做什么?)
-
创建Thread类的子类对象
-
调用Thread类中的方法start方法,开启新的线程,执行run方法
void start( )使线程开始执行
结果是两个线程并发地运行,当前线程(主线程)和零i个线程(创建的新线程,执行其run方法)
多次启动一个线程是非法的,特别是当线程已经结束执行后,不能再重新启动
java属于抢占式调度,哪个优先级高,优先执行哪个
Thread类常用方法
-
获取线程名称:
- String getName( ) 返回该线程的名称
- 可以用static Thread currentThread( )先获取到当前正在执行的线程,使用线程中的getName( )获取线程的名称
-
设置线程名称:
- 使用Thread类中的方法setName( )
- 创建一个带参数的构造方法,参数传递线程的名称,调用父类的构造方法,把线程名称传递给父类,让父类(Thread)给子线程起一个名字
-
暂停执行线程:
public static void sleep(long millis):使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行),毫秒数结束之后,线程继续执行
第二种:实现Runnable接口
- 创建一个Runnable接口的实现类
- 在实现类中重写Runnable接口的run方法,设置线程任务
- 创建一个Runnable接口的实现类对象
- 创建Thread类对象,构造方法中传递Runnable接口的实现类对象
- 调用Thread类中的start方法,开启新的线程执行run方法
Thread和Runnable的区别
实现Runnable接口创建多线程程序的好处:
-
避免了单继承的局限性
一个类只能继承一个类(一个人只能有一个亲爹),类继承了Thread类就不能继承其他的类
-
增强了程序的扩展性,降低了程序的耦合性(解耦)
实现Runnable接口的方式,把设置线程任务和开启线程进行了分离(解耦)
实现类中,重写了run方法,用来设置线程任务
创建Thread类对象,调用start方法,用来开启新线程
匿名内部类实现线程的创建
格式:
new 父类/接口( ){
重写父类/接口的方法
}
public class Demo01 {
public static void main(String[] args) throws InterruptedException {
//线程的父类是Thread
//开启线程:new Thread.start()
new Thread(){
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "第一种");
}
}.start();
Runnable r = new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "第二种");
}
};
new Thread(r).start();
//简化
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "简化第二种");
}
}).start();
}
}
线程安全
概述
- 单线程程序不会出现线程安全问题(一个窗口卖票)
- 多线程程序,没有访问共享数据,不会出现问题(多个窗口卖不同的票)
- 多线程程序,访问共享的数据,会出现安全问题(多个窗口卖一样的票,可能会卖出同样编号的票)
线程安全问题是不可以产生的,我们可以让一个线程在访问共享数据时,无论是否失去了cpu的执行权,都让其他线程等待,等当前线程执行完毕,其他线程再执行
共有三种解决办法:同步代码块,同步方法,Lock锁
同步代码块
格式:synchronized(锁对象){
可能会出现线程安全问题的代码(访问了共享数据的代码)
}
注意:
- 同步代码块中的锁对象,可以使用任意的对象
- 但是必须保证多个线程使用的锁对象是同一个
- 锁对象作用:把同步代码块锁住,只让一个线程在同步代码块中执行
package pack1;
/**
* @Author: 2019012364 袁梦达 3班
*/
public class RunnableImpl implements Runnable{
private int tickets = 100;
Object obj = new Object();
@Override
public void run() {
while (true){
synchronized (obj){
if(tickets > 0){
System.out.println(Thread.currentThread().getName() + "正在卖第" + tickets + "张票");
tickets--;
}
}
}
}
}
package pack1;
public class Demo01 {
public static void main(String[] args) throws InterruptedException {
Runnable run = new RunnableImpl();
Thread thread1 = new Thread(run);
Thread thread2 = new Thread(run);
Thread thread3 = new Thread(run);
thread1.start();
thread2.start();
thread3.start();
}
}
原理:使用了一个锁对象,这个锁对象叫同步锁,也叫对象锁,也叫对象监视器。同步中的线程,没有执行完毕不会释放锁,同步外的线程没有锁进不去同步。
同步方法
使用步骤:
- 把访问了共享数据的代码抽取出来,放到一个方法中
- 在方法上添加synchronized修饰符
格式:修饰符 synchronized 返回值类型 方法名(参数列表){
}
同步方法也会把方法内部的代码锁住,只让一个线程执行。
锁对象就是实现类 new RunnableImpl,也就是this
Lock锁
Lock接口中的方法:
- void lock( )获取锁
- void unlock( )释放锁
使用步骤:
- 在成员位置创建一个ReentrantLock对象
- 在可能会出现安全问题的代码前调用Lock接口中的方法lock获取锁
- 在可能会出现安全问题的代码后调用Lock接口中的方法unlock释放锁
package pack1;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @Author: 2019012364 袁梦达 3班
*/
public class RunnableImpl implements Runnable{
private int tickets = 100;
Lock l = new ReentrantLock();
@Override
public void run() {
while (true){
l.lock();
try{
Thread.sleep(10);
if(tickets > 0){
System.out.println(Thread.currentThread().getName() + "正在卖第" + tickets + "张票");
tickets--;
}
}catch (InterruptedException e){
e.printStackTrace();
}finally {
l.unlock();
}
}
}
}
线程状态
概述
新建状态(New)
当用new操作符创建一个线程后, 例如new Thread®,此时线程处在新建状态。 当一个线程处于新建状态时,线程中的任务代码还没开始运行。
就绪状态(Runnable)
也被称为“可执行状态”。一个新创建的线程并不自动开始运行,要执行线程,必须调用线程的start()方法。当调用了线程对象的start()方法即启动了线程,此时线程就处于就绪状态。
处于就绪状态的线程并不一定立即运行run()方法,线程还必须同其他就绪线程竞争CPU,只有获得CPU使用权才可以运行线程。比如在单核心CPU的计算机系统中,不可能同时运行多个线程,一个时刻只能有一个线程处于运行状态。对与多个处于就绪状态的线程是由Java运行时系统的线程调度程序(thread scheduler)来调度执行。
除了调用start()方法后让线程变成就绪状态,一个线程阻塞状态结束后也可以变成就绪状态,或者从运行状态变化到就绪状态。
运行状态(Running)
线程获取到CPU使用权进行执行。需要注意的是,线程只能从就绪状态进入到运行状态。真正开始执行run()方法的内容。
阻塞状态(Blocked)
线程在获取锁失败时(因为锁被其它线程抢占),它会被加入锁的同步阻塞队列,然后线程进入阻塞状态(Blocked)。处于阻塞状态(Blocked)的线程放弃CPU使用权,暂时停止运行。待其它线程释放锁之后,阻塞状态(Blocked)的线程将在次参与锁的竞争,如果竞争锁成功,线程将进入就绪状态(Runnable) 。
等待状态(WAITING)
或者叫条件等待状态,当线程的运行条件不满足时,通过锁的条件等待机制(调用锁对象的wait()或显示锁条件对象的await()方法)让线程进入等待状态(WAITING)。处于等待状态的线程将不会被cpu执行,除非线程的运行条件得到满足后,其可被其他线程唤醒,进入阻塞状态(Blocked)。调用不带超时的Thread.join()方法也会进入等待状态。
限时等待状态(TIMED_WAITING)
限时等待是等待状态的一种特例,线程在等待时我们将设定等待超时时间,如超过了我们设定的等待时间,等待线程将自动唤醒进入阻塞状态(Blocked)或就绪状态(Runnable) 。在调用Thread.sleep()方法,带有超时设定的Object.wait()方法,带有超时设定的Thread.join()方法等,线程会进入限时等待状态(TIMED_WAITING)。
死亡状态(TERMINATED)
线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
等待唤醒案例
创建一个顾客线程(消费者):告知老板要的包子种类和数量,调用wait方法,放弃cpu的执行,进入WATTING状态(无限等待)
创建一个老板线程(生产者):花了5秒做包子,做好包子之后,调用notify方法,唤醒顾客吃包子
注意:
顾客和老板线程必须使用同步代码块包裹起来,保证等待和唤醒只有一个在执行。
同步使用的锁对象必须保证唯一。
只有锁对象才能调用wait和notify方法
void wait( ):在其他线程调用此对象的notify( )或notifyAll( )方法前,导致当前线程等待
void notify( ):唤醒在此对象监视器上等待的单个线程。会继续执行wait方法之后的代码
package pack1;
public class Demo01 {
public static void main(String[] args) throws InterruptedException {
//创建锁对象,保证唯一
Object obj = new Object();
//创建顾客线程
new Thread(){
@Override
public void run() {
//保证等待和唤醒的线程只有一个,需要使用同步技术
synchronized (obj){
System.out.println("告知老板要的包子数量和种类");
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
//唤醒之后的代码
System.out.println("包子真好吃!");
}
}
}.start();
//创建老板线程
new Thread(){
@Override
public void run() {
//花5秒钟做包子
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj){
System.out.println("包子做好了!");
obj.notify();
}
}
}.start();
}
}
进入计时等待有两种方式:
- sleep(long m)
- wait(long m):如果毫秒值结束后,还没有被notify唤醒,就会自动醒来
唤醒的方法:
- notify
- notifyAll 唤醒所有等待线程
线程池
概述
线程池:容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多资源
###代码实现
Executors:线程池的工厂类,用来生成线程池
Executors中的静态方法:static ExecutorService newFixedThreadPool(int nThreads) 创建一个可重用固定线程数的线程池
参数:线程池中包含的线程数量
返回值: ExecutorService接口,用来从线程池种获取线程
接口中的方法:
- submit(Runnable task) 提交一个Runnable任务用于执行
- void shudown( )关闭/销毁线程池的方法
使用步骤
- 使用线程池的工厂类Executors里面的静态方法newFixedThreadPool生产一个指定线程数量的线程池
- 创建一个类,实现Runnable接口,重写run方法,设置线程任务
- 调用ExecutorService中的方法submit,传递线程任务(实现类),开启线程,执行run方法
- 调用ExecutorService中的shutdown销毁线程池(不推荐)
package pack1;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @Author: 2019012364 袁梦达 3班
*/
public class RunnableImpl implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "创建了一个新线程");
}
}
package pack1;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @Author: 2019012364 袁梦达 3班
*/
public class ThreadPool {
public static void main(String[] args) {
ExecutorService es = Executors.newFixedThreadPool(2);
es.submit(new RunnableImpl());
es.submit(new RunnableImpl());
es.submit(new RunnableImpl());
es.shutdown(); //销毁线程池(不建议执行)
es.submit(new RunnableImpl()); //抛异常
}
}
Lambda表达式
写法
package pack1;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @Author: 2019012364 袁梦达 3班
*/
public class ThreadPool {
public static void main(String[] args) {
//匿名内部类实现多线程
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "创建了新线程");
}
}).start();
//Lambda表达式实现多线程
new Thread(()->{
System.out.println(Thread.currentThread().getName() + "创建了新线程");
}
).start();
}
}
特点:延迟加载
标准格式
三部分组成
- 一些参数
- 一个箭头
- 一段代码
(参数列表)->{ 重写方法的代码 };
解释说明格式:
- ( ):接口中抽象方法的参数列表,没有参数就空着;有参数就写出参数,多个参数使用逗号分隔
- ->:传递的意思,把参数传递给方法体
- { }:重写接口的抽象方法的方法体
省略格式
可以省略的内容:
-
(参数列表):括号中参数列表的数据类型可以省略不写
-
(参数列表):括号中的参数如果只有一个,那么类型和()都可以省略
-
{一些代码}:如果{ }中的代码只有一行,无论是否有返回值,都可以省略{ }, return, 分号
注意:要省略,{ }, return, 分号必须一起省略
使用前提
-
使用Lambda表达式必须具有接口,且要求接口中有且仅有一个抽象方法
-
使用Labmda必须具有上下文推断
也就是方法的参数或局部变量类型必须为Lambda对应的接口类型
File类
概述
文件和目录路径名的抽象表示形式,可以对文件和文件夹(目录)进行操作
- 创建文件/文件夹
- 删除文件/文件夹
- 获取文件/文件夹
- 判断文件/文件夹是否存在
- 遍历
- 获取文件的大小
记住三个单词:
- file:文件
- directory:文件/目录
- path:路径
File类的静态成员变量
- static String pathSeparator:与系统有关的路径分隔符,为方便,它被表示为一个字符串
- static char pathSeparatorChar:与系统有关的路径分隔符
- static String separator:与系统有关的默认名称分隔符,为了方便,它被表示为一个字符串
- static char SeparatorChar:与系统有关的默认名称分隔符
路径分隔符 windows为分号; Linux为冒号:
文件名称分隔符 windows为反斜杠\ Linux为正斜杠/
所以路径不能写死,比如c:\a\b\c.txt需要写成"c: + File.separator + “a” + File.separator + “b” + File.separator + “c.txt”"
File类的构造方法
-
File(String pathname):通过将给定路径名字符串转换为抽象路径名来创建一个File实例
路径可以以文件结尾,也可以以文件夹结尾
可以是相对路径, 也可以是绝对路径
路径可以存在,也可以不存在
-
File(String parent, String child):根据parent路径名字符串和child路径名字符串创建一个新File实例
parent:父路径
child:子路径
好处:父路径和子路径可以单独书写,使用起来非常灵活,两者都可以变化
-
File(File parent, String child):根据parent抽象路径名和child路径名字符串创建一个新File实例
parent:父路径
child:子路径
好处:父路径和子路径可以单独书写,使用起来非常灵活,两者都可以变化;父路径是File类,可以使用File类的方法进行一些操作
File类获取的方法
- public String getAbsolutePath( ):返回此File的绝对路径名字符串
- public String getPath( ):将此File转换为路径名字符串
- public String getName( ):返回由此File表示的文件或目录的名称
- public long length( ):返回由此File表示的文件的长度(以字节为单位),如果路径不存在,返回0;文件夹没有大小概念,所以也返回0
File类判断的方法
- public boolean exists( ):判断此File表示的文件或目录是否存在
- public boolean isDirectory( ):判断此File表示的是否为目录(前提是路径存在,不然返回false)
- public boolean isFile( ):判断此File表示的是否为文件(前提是路径存在,不然返回false)
File类创建删除的方法
-
public boolean createNewFile( ):当且仅当具有该名称的文件尚不存在时,创建一个新的空文件(只能创建文件,不能创建文件夹)
这个方法声明抛出了IOException, 调用这个方法必须处理这个异常,要么throws,要么try…catch
-
public boolean delete( ):删除由此File表示的文件或目录(直接从硬盘中删除,不进回收站,删除要谨慎)
-
public boolean mkdir( ):创建由此File表示的目录(只能创建单级文件夹)
-
public boolean mkdirs( ):创建由此File表示的目录,包括任何必需但不存在的父目录(可以创建多级文件夹)
File类中遍历文件夹的方法
- public String[ ] list( ):返回一个String数组,表示该File目录中的所有子文件或目录
- public File[ ] listFiles( ):返回一个File数组,表示该File目录中的所有的子文件或目录
注意:
- 如果给出的目录的路径不存在,会抛出空指针异常。
- 如果给出的路径不是一个目录,也会抛出空指针异常。
- 可以遍历隐藏的文件和文件夹
FileFilter过滤器
在File类中有两个和listFiles重载的方法,方法的参数传递的就是过滤器
-
File[ ] listFiles(FileFilter filter)
FileFilter接口:用于抽象路径名的过滤器,用来过滤文件
抽象方法:用来过滤文件的方法
boolean accept(File pathname) 测试指定抽象路径名是否应该包含在某个路径名列表中
-
File[ ] listFiles(FilenameFilter filter)
FilenameFilter接口:用来过滤文件名称
抽象方法:用来过滤文件的方法
boolean accept(File dir, String name) 测试指定文件是否应该包含在某一文件列表中
IO流
I:input输入(读取):把硬盘中的数据读取到内存中使用
O:output输出(写入):把内存中的数据写入到硬盘中保存
流:数据(字符,字节)1个字符 = 2个字节 ,1个字节 = 8位
输入流 | 输出流 | |
---|---|---|
字节流 | 字节输入流InputStream | 字节输出流OutputStream |
字符流 | 字符输入流Reader | 字符输出流Writer |
字节流
字节输出流OutputStream
表示字节输出流的所有类的超类
共性成员方法:
- public void close( ):关闭此输出流并释放与此输出流相关联的任何系统资源
- public void flush( ):刷新此输出流并强制任何缓冲的输出字节被写出
- public void write(byte[ ] b):将b.length字节从指定的字节数组写入此输出流
- public void write(byte[ ] b, int off, int len):从指定的字节数组写入len字节,从偏移量off开始输出到此输出流
- public abstract void write(int b):将指定的字节输出流
####FileOutputStream
文件字节输出流,继承了OutputStream
作用:把内存中的数据写入到硬盘中
#####构造方法
-
FileOutputStream(String name):创建一个向具有指定名称的文件(文件路径)中写入数据的输出文件流
-
FileOutputStream(File file):创建一个向指定File对象表示的文件中写入数据的文件输出流
构造方法的作用:
- 创建一个FileOutputStream对象
- 根据构造方法中传递的文件/文件路径,创建一个空的文件
- 会把FileOutputStream对象指向创建好的文件
写出字节数据
写入数据的原理(内存–>硬盘)
java程序–>JVM(java虚拟机)–>OS(操作系统)–>OS调用写数据的方法–>把数据写入到文件中
字节输出流的使用步骤:
- 创建一个FileOutputStream对象,构造方法中传递写入数据的目的地
- 调用FileOutputStream对象中的方法write,把数据写入到文件中
- 释放资源(流使用会占用一定的内存,使用完毕要清空,提高效率)
写出多个字节的办法
public void write(byte[ ] b):将b.length字节从指定的字节数组写入此输出流
public void write(byte[ ] b, int off, int len):从指定的字节数组写入len字节,从偏移量off开始输出到此输出流
off:数组开始的索引, len:写几个字节
如果写的第一个字节是正数(0-127),那么显示的时候会查询ASCII表
如果写的第一个字节是负数,那第一个字节会和第二个字节组成一个中文显示,查询系统默认码表(GBK)
写入字符的方法:
使用String类中的getBytes( )把字符串转换为字节数组,然后使用wrtie
字节输入流InputStream
表示字节输入流的所有类的超类
共性方法
- int read( )从输入流中读取数据的下一个字节
- int read(byte[ ] b)从输入流中读取一定数量的字节,并将其储存在缓冲区数组b中
- void close( )关闭此输入流并释放与此输入流相关联的任何系统资源
FileInputStream
文件字节输入流,继承了InputStream
作用:把硬盘文件中的数据,读取到内存中使用
构造方法
-
FileIntputStream(String name)
-
FileIntputStream(File file)
构造方法的作用:
- 创建一个FileInputStream对象
- 会把FileInputStream对象指向构造方法中要读取的文件
读取多个字节的办法
int read(byte[ ] b)从输入流中读取一定数量的字节,并将其储存在缓冲区数组b中
byte[ ]起到缓冲作用,存储每次读取到的多个字节,数组长度一般定义为1024(1kb)或1024的整数倍
方法的返回值int表示每次读取的有效字节的个数
String(byte[ ] bytes):把字节数组转换为字符串
String(byte[ ] bytes, int offset, int length):把字符数组的一部分转换为字符串,offset为数组开始的索引,length为转换的字节个数
package pack1;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.String; //!!!注意不要导错包!!!!!
public class Demo01 {
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("D:\\Ego\\Java\\p1\\src\\pack1\\a");
int len = 0;
byte[] bytes = new byte[1024];
while ((len = fis.read(bytes)) != -1){
//System.out.println(new String(bytes)); 后面会打印很多个空格
System.out.println(new String(bytes, 0, len));
}
fis.close();
}
}
字符流
使用字节流读取文本文件时,遇到中文字符可能会显示不完整,因为一个中文字符可能占用多个字节,所以java提供了一些字符流,以字符为单位读写数据,专门用于处理文本文件
字符输入流Reader
Reader是字符输入流的超类,是一个抽象类
共性成员方法:
- int read( ) 读取单个字符并返回
- int read(char[ ] cbuf) 一次读取多个字符,将字符存入数组
- void close( ) 关闭该流并释放与之关联的所有资源
FileReader
FileReader extends InputStreamReader extends Reader
作用:把硬盘文件中的数据以字符的方式读取到内存中
构造方法
- FileReader(String filename)
- FileReader(File file)
读取字符数据
使用步骤:
- 创建FileReader对象,构造方法中绑定要读取的数据源
- 使用FileReader对象中的方法read读取文件
- 释放资源
字符输出流Writer
字符输出流的超类,是一个抽象类
共性的成员方法:
- void write(int c)写入单个字符
- void write(char[ ] cbuf) 写入字符数组
- abstract void write(char[ ] cbuf, int off, int len) 写入字符数组的某一部分,off为数组的开始索引,len为写的字符个数
- void write(String str)写入字符串
- void write(String str, int off, int len) 写入字符串的某一部分,off为字符串的开始索引,len为写的字符个数
- void flush( )刷新该流的缓冲
- void close( )关闭此流,但要先刷新它
FileWriter
FileWriter extends OutputStreamWriter extends Writer
作用:把内存中字符数据写入到文件中
构造方法
- FileWriter(File file)
- FileWriter(String fileName)
写出字符数据
步骤:
- 创建FileWriter对象,构造方法中绑定要写入数据的目的地
- 使用FileWriter中的方法write,把数据写入到内存缓冲区中(字符转换为字节的过程)
- 使用FileWriter中的方法flush,把内存缓冲区的数据,刷新到文件中
- 释放资源(会先把内存缓冲区的数据刷新到文件中)
续写和换行
续写,追加写:使用两个参数的构造方法
-
FileWriter(String fileName, boolean append)
-
FileWriter(File file, boolean append)
参数:String fileName, File file:写入目的地
boolean append:续写开关,true:不会创建新的文件覆盖原文件,可以续写;false:创建新的文件覆盖原文件
换行:换行符号
- windows:\r\n
- Linux:/n
- mac:/r
流中异常的处理
####JDK7新特性
在try后边可以增加一个( ),括号里定义流对象
那么这个流对象的作用域就在try中有效,try中的代码执行完毕会自动把流对象释放,不用写finally
格式:
try(定义流对象;定义流对象…){
}catch( ){
}
JDK9新特性
try的前边可以定义流对象
在try后边的( )中可以直接引入流对象的名称(变量名)
在try代码执行完毕之后,流对象也可以释放掉,不用写finally
格式:
A a = new A( );
B b = new B( );
try(a;b){
}catch( ){
}
Properties集合
extends Hashtable<k, v> implements Map<k, v>
表示了一个持久的属性集,可保存在流中或从流中加载,属性列表中每个键及其对应值都是一个字符串
Porperties集合是一个唯一和IO流相结合的集合
- 可以使用集合中的方法store,把集合中的临时数据持久化写入到硬盘中存储
- 可以使用集合中的方法load,把硬盘中保存的文件(键值对),读取到集合中使用
Properties是一个双列集合,key和value默认都是字符串
- Object setProperty(String key, String value) 调用Hashtable的方法put
- String getProperty(String key) 通过key找到value,相当于Map集合中的get(key)方法
- Set stringPropertyNames( )返回此属性列表中的键集,其中该键及其对应值是字符串,此方法相当于Map集合中的keySet方法
store方法
-
void store(OutputStream out, String comments)
-
void store(Writer writer, String comments)
OutputStream out:字节输出流,不能写入中文
Writer writer:字符输出流,可以写入中文
String comments:注释,用来解释说明保存的文件是做什么用的,注释不能使用中文,会产生乱码,默认是Unicode编码,一般使用空字符串’’’’
步骤:
- 创建Properties集合对象,添加数据
- 创建字节输出流/字符输出流对象,构造方法中绑定要输出的目的地
- 使用Properties中的store方法,把集合中的临时数据,持久化写入到硬盘中存储
- 释放资源
load方法
-
void load(InputStream inStream)
-
void load(Reader reader)
InputStream inStream:字节输入流,不能读取含有中文的键值对
Reader reader:字符输入流,能读取含有中文的键值对
步骤:
- 创建Properties集合对象
- 使用Properties中的load方法读取保存键值对的文件
- 遍历Properties集合
注意:
- 存键值对的文件,键与值默认的连接符号可以使用=,空格(其他符号)
- 存键值对的文件,可以使用#进行注释,被注释的键值对不会再被读取
- 存键值对的文件,键与值默认都是字符串,不用再加引号
缓冲流
概述
缓冲流也叫高效流,是对四个基本的FileXxx流的增强
原理:创建流对象时,创建一个内置的默认大小的缓冲区数组,通过缓冲区读写,减少系统IO次数,从而提高读写的效率
字节缓冲流:BufferedInputStream BufferedOutputSteam
字符缓冲流:BufferedReader BufferedWriter
字节缓冲输出流
BufferedOutputSteam继承OutputStream,所以可以使用父类的共性成员方法
- public void close( ):关闭此输出流并释放与此输出流相关联的任何系统资源
- public void flush( ):刷新此输出流并强制任何缓冲的输出字节被写出
- public void write(byte[ ] b):将b.length字节从指定的字节数组写入此输出流
- public void write(byte[ ] b, int off, int len):从指定的字节数组写入len字节,从偏移量off开始输出到此输出流
- public abstract void write(int b):将指定的字节输出流
构造方法:
- BufferedOutputStream(OutputStream out) 创建一个新的缓冲输出流,以将数据写入指定的底层输出流
- BufferedOutputStream(OutputStream out, int size) 创建一个新的缓冲输出流,以将具有指定缓冲区大小的数据写入指定的底层输出流
使用步骤:
- 创建FileOutputStream对象,构造方法中绑定要输出的目的地
- 创建BufferedOutputStream对象,构造方法中传递FileOutputStream对象,提高FileOutputStream对象效率
- 使用BufferedOutputStream对象中的方法write,把数据写入到内部缓冲区中
- 使用BufferedOutputStream对象中的方法flush,把内部缓冲区的数据,刷新到文件中
- 释放资源(会先调用flush方法刷新,所以第4步可以省略)
字节缓冲输入流
BufferedInputStream继承InputStream, 所以可以使用父类的共性成员方法
- int read( )从输入流中读取数据的下一个字节
- int read(byte[ ] b)从输入流中读取一定数量的字节,并将其储存在缓冲区数组b中
- void close( )关闭此输入流并释放与此输入流相关联的任何系统资源
构造方法:
- BufferedInputStream(InputStream in)
- BufferedInputStream(InputStream in, int size)
使用步骤:
- 创建FileInputStream对象,构造方法中绑定要读取的数据源
- 创建BufferedInputStream对象,构造方法中传递FileInputStream对象,提高FileInputStream对象的读取效率
- 使用BufferedInputStream对象中的方法read,读取文件
- 释放资源
字符缓冲输出流
BufferedWriter继承Writer,所以可以使用父类的共性成员方法
- void write(int c)写入单个字符
- void write(char[ ] cbuf) 写入字符数组
- abstract void write(char[ ] cbuf, int off, int len) 写入字符数组的某一部分,off为数组的开始索引,len为写的字符个数
- void write(String str)写入字符串
- void write(String str, int off, int len) 写入字符串的某一部分,off为字符串的开始索引,len为写的字符个数
- void flush( )刷新该流的缓冲
- void close( )关闭此流,但要先刷新它
构造方法:
- BufferedWriter(Writer out)
- BufferedWriter(Writer out, int sz)
特有的成员方法:
void newLine( ):写入一个行分隔符。会根据不同的操作系统,获取不同的分隔符
字符缓冲输入流
BufferedReader继承Reader,所以可以使用父类的共性成员方法
- int read( ) 读取单个字符并返回
- int read(char[ ] cbuf) 一次读取多个字符,将字符存入数组
- void close( ) 关闭该流并释放与之关联的所有资源
构造方法:
- BufferedReader(Reader in)
- BufferedReader(Reader in, int sz)
特有的成员方法:
String readLine( ):读取一个文本行,读取一行数据
行的终止符号:通过下列字符之一即可认为某行已终止:换行(’\n’)、回车(’\r’)或回车后直接跟着换行(’\r\n’)
返回值:包含该行内容的字符串,不包含任何终止符,如果已到达流末尾,则返回null
转换流
OutputStreamWriter
继承了Writer
是字符流通向字节流的桥梁,可以使用指定的charset将要写入流中的字符编码成字节(编码:把能看懂的变成看不懂的)
构造方法:
- OutputStreamWriter(OutputStream out) 创建使用默认字符编码的OutputStreamWriter
- OutputStreamWriter(OutputStream out, String charsetName) 创建使用指定字符集的OutputStreamWriter
charsetName不区分大小写,不指定的话默认使用UTF-8
使用步骤:
- 创建OutputStreamWriter对象,构造方法中传递字节输出流和指定的编码名称
- 使用OutputStreamWriter中的write方法,把字符转换为字节储存缓冲区(编码)
- OutputStreamWriter中的flush方法,把内存缓冲区的字节刷新到文件中
- 释放资源
InputStreamReader
继承了Reader
是字节流流向字符流的桥梁,使用指定的charset读取字节并将其解码为字符(解码:把看不懂的变成能看懂的)
构造方法:
- InputStreamReader(InputStream in) 创建使用默认字符编码的InputStreamReader
- InputStreamReader(InputStream in, String charsetName) 创建使用指定字符集的InputStreamReader
charsetName不区分大小写,不指定的话默认使用UTF-8
使用步骤:
- 创建InputStreamReader对象,构造方法中传递字节输入流和指定的编码表名称
- 使用InputStreamReader的read读取文件
- 释放资源
注意:构造方法中指定的编码表名称要和文件的编码相同,否则会发生乱码
序列化
把对象以流的方式写入到文件中保存,叫写对象,也叫对象的序列化
把文件中保存的对象,以流的方式读取出来,叫读对象,也叫对象的反序列化
注意:类必须实现Serializable接口,才能进行序列化和反序列化,不然会抛出NotSerializableException异常
对象的序列化
ObjectOutputStream extends OutputStream
构造方法:
- ObjectOutputStream(OutputStream out)创建写入指定OutputStream的ObjectOutputStream
特有的成员方法:
void writeObject(Object obj)将指定的对象写入ObjectOutputStream
使用步骤:
- 创建ObjectOutputStream对象,构造方法中传递字节输出流
- 使用ObjectOutputStream对象中的writeObject方法,把对象写入到文件中
- 释放资源
对象的反序列化
ObjectInputStream extends InputStream
构造方法:
- ObjectInputStream(InputStream in)创建写入指定InputStream的ObjectInputStream
特有的成员方法:
void readObject(Object obj) 从ObjectInputStream读取对象
使用步骤:
- 创建ObjectInputStream对象,构造方法中传递字节输入流
- 使用ObjectInputStream对象中的readObject方法读取保存对象的文件
- 释放资源
- 使用读取出来的对象
transient关键字
被static关键字修饰的成员变量不能被序列化,因为序列化的都是对象,但是静态优先于对象进入到内存,不属于对象
transient关键字 :瞬态关键字
被transient修饰的成员变量不可以被序列化
InvalidClassException
如果一个类被序列化之后进行了修改,那么反序列化会抛出InvalidClassException异常。因为每次类被修改时都会生成一个新的序列号serialVersionUID。
如果想要改动类却不改序列号的话,使用 static final long serialVersionUID = xxx,把它设为常量
打印流
PrintStream为其他输出流添加了功能,使它们能够方便地打印各种数据值表示形式
特点:
- 只负责数据的输出,不负责数据的读取
- 与其他输出流不同,PrintStream永远不会抛出IOException
- 有特有的方法:print,println
构造方法:
- PrintStream(File file)输出的目的地是一个文件
- PrintStream(OutputStream out)输出的目的地是一个字节输出流
- PrintStream(String fileName)输出的目的地是一个文件路径
PrintStream extends OutputStream
注意:如果使用继承自父类的write方法写数据,那么查看数据的时候会查询编码表 97->a;如果使用自己特有的方法print/println方法写数据,写的数据原样输出 97->97
网络编程
TCP通信程序
概述
客户端和服务端通信步骤:
- 服务端程序,需要事先启动,等待客户端的连接
- 客户端主动连接服务器端,连接成功才能通信。服务端不可以主动连接客户端
服务器端必须明确的两件事情:
-
多个客户端同时和服务器端进行交互,服务器端必须明确和哪个客户端进行的交互。服务器端有一个方法叫accept,获取到请求的客户端对象
-
多个客户端和服务器交互,就需要多个IO流对象
服务器端是没有IO流的,服务器端可以获取到请求的客户端对象Socket,使用每个客户端Socket中提供的IO流和客户端进行交互
客户端类:Socket
服务端类:ServerSocket
Socket类
此类实现客户端套接字。套接字是两台机器间通信的端点,包含了IP地址和端口号的网络单位
构造方法
Socket(String host, int port) 创建一个流套接字并将其连接到指定主机上的指定端口号
参数:
String host:服务器主机的名称/服务器的IP地址
int port:服务器的端口号
成员方法
- OutputStream getOutputStream( )返回此套接字的输出流
- InputStream getInputStream( )返回此套接字的输入流
- void close( )关闭此套接字
实现步骤
- 创建一个客户端对象Socket,构造方法绑定服务器的IP地址和端口号
- 使用Socket对象中的方法getOutputStream获取网络字节输出流OutputStream对象
- 使用OutputStream对象中的方法write,给服务器发送数据
- 使用Socket对象中的方法getInputStream获取网络字节输入流InputStream对象
- 使用InputStream对象中的方法read,读取服务器回写的数据
- 释放资源(Socket)
注意:
-
客户端和服务器端进行交互,必须使用Socket中提供的网络流,不能使用自己创建的流对象
-
当我们创建客户端对象Socket的时候,就会去请求服务器经过3次握手建立连接通路。
这时如果服务器没有启动,那么就会抛出异常
如果服务器已经启动,那么就可以进行交互了
ServerSocket类
此类实现服务器套接字
构造方法
ServerSocket(int port) 创建绑定到特定端口的服务器套接字
服务器端必须明确一件事情:必须知道是哪个客户端请求的服务器,所以可以使用accept方法获取到请求的客户端对象Socket
成员方法
Socket accept( ) 侦听并接收到此套接字的连接
实现步骤
- 创建服务器ServerSocket对象和系统指定的端口号
- 使用ServerSocket对象中的方法accept,获取到请求的客户端对象Socket
- 使用Socket对象中的方法getInputStream获取网络字节输入流InputStream对象
- 使用网络字节输入流InputStream对象中的方法read,读取客户端发送的数据
- 使用Socket对象中的方法getOutputStream获取网络字节输出流OutputStream对象
- 使用网络字节输出流OutputStream对象的方法write,给客户端回写数据
- 释放资源(Socket,ServerSocket)
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
/**
* @Author: 2019012364 袁梦达 3班
*/
public class Client {
public static void main(String[] args) throws IOException {
//创建一个客户端对象Socket,构造方法绑定服务器的IP地址和端口号
Socket socket = new Socket("127.0.0.1", 5555);
//使用Socket对象中的方法getOutputStream获取网络字节输出流OutputStream对象
OutputStream os = socket.getOutputStream();
//使用OutputStream对象中的方法write,给服务器发送数据
os.write("你好服务端".getBytes());
//使用Socket对象中的方法getInputStream获取网络字节输入流InputStream对象
InputStream is = socket.getInputStream();
//使用InputStream对象中的方法read,读取服务器回写的数据
byte[] bytes = new byte[1024];
int len = is.read(bytes);
System.out.println(new String(bytes, 0, len));
//释放资源(Socket)
socket.close();
}
}
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
/**
* @Author: 2019012364 袁梦达 3班
*/
public class Server {
public static void main(String[] args) throws IOException {
//创建服务器ServerSocket对象和系统指定的端口号
ServerSocket serverSocket = new ServerSocket(5555);
//使用ServerSocket对象中的方法accept,获取到请求的客户端对象Socket
Socket socket = serverSocket.accept();
//使用Socket对象中的方法getInputStream获取网络字节输入流InputStream对象
InputStream is = socket.getInputStream();
//使用网络字节输入流InputStream对象中的方法read,读取客户端发送的数据
byte[] bytes = new byte[1024];
int len = is.read(bytes);
System.out.println(new String(bytes, 0, len));
//使用Socket对象中的方法getOutputStream获取网络字节输出流OutputStream对象
OutputStream os = socket.getOutputStream();
//使用网络字节输出流OutputStream对象的方法write,给客户端回写数据
os.write("收到了!谢谢!".getBytes());
//释放资源(Socket,ServerSocket)
socket.close();
serverSocket.close();
}
}
上传文件案例
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
/**
* @Author: 2019012364 袁梦达 3班
*/
public class Client {
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("C:\\Users\\86156\\Desktop\\11\\pic.jpg");
Socket socket = new Socket("127.0.0.1", 8888);
OutputStream os = socket.getOutputStream();
int len;
byte[] bytes = new byte[1024];
while ((len = fis.read(bytes)) != -1){
os.write(bytes, 0, len);
}
socket.shutdownOutput();
InputStream is = socket.getInputStream();
while ((len = is.read(bytes)) != -1){
System.out.println(new String(bytes, 0, len));
}
fis.close();
socket.close();
}
}
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
/**
* @Author: 2019012364 袁梦达 3班
*/
public class Server {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(8888);
while (true){
new Thread(new Runnable() {
@Override
public void run() {
try{
Socket socket = serverSocket.accept();
InputStream is = socket.getInputStream();
String filename = "\\" + System.currentTimeMillis() + Math.random() + ".jpg";
File file = new File("C:\\Users\\86156\\Desktop\\22");
if(!file.exists()){
file.mkdirs();
}
FileOutputStream fos = new FileOutputStream(file + filename);
int len = 0;
byte[] bytes = new byte[1024];
while ((len = is.read(bytes)) != -1){
fos.write(bytes, 0, len);
}
socket.getOutputStream().write("上传成功".getBytes());
fos.close();
socket.close();
}catch (IOException e){
e.printStackTrace();
}
}
}).start();
}
}
}
函数式接口
概念
函数式接口:有且仅有一个抽象方法的接口
适用于函数式编程场景的接口(适用于Lambda)
@FunctionalInterface注解
可以检测接口是否是一个函数式接口
是:编译通过,不是:编译失败
常用的函数式接口
Supplier接口
Supplier接口仅包含一个无参的方法:T get()用来获取一个泛型参数指定类型的对象数据
被称之为生产型接口,指定接口的泛型是什么类型,那么接口中的get方法就会产生什么类型的数据
Consumer接口
Consumer接口正好与Supplier接口相反,它不是生产一个数据,而是消费一个数据,其数据类型由泛型决定
包含抽象方法 void accept(T t),意为消费一个指定泛型的数据
Consumer接口的默认方法andThen
作用:需要两个Consumer接口,可以把两个Consumer接口组合到一起,再对数据进行消费
import java.util.function.Consumer;
/**
* @Author: 2019012364 袁梦达 3班
*/
public class Demo {
public static void method(String name, Consumer<String> con1, Consumer<String> con2){
//con1.accept(name);
//con2.accept(name);
con1.andThen(con2).accept(name);
}
public static void main(String[] args) {
method("HeLLo", s-> System.out.println(s.toLowerCase()), s-> System.out.println(s.toUpperCase()));
}
}
Predicate接口
作用:对某种数据类型的数据进行判断,结果返回一个boolean值
接口中包含一个抽象方法 boolean Test(T t):用来对指定数据类型数据进行判断的方法
接口中由三个默认方法:
- and 与
- or 或
- negate 非
Function接口
Function<T, R>据一个类型的数据得到另一个类型的数据,前者称为前置条件,后者称为后置条件
接口中最主要的抽象方法 R apply(T t),根据类型T的参数获取类型R的结果
接口中的默认方法andThen,用来进行组合操作
Stream流
Stream流是JDK1.8之后出现的,关注的是做什么,而不是怎么做
import java.util.ArrayList;
import java.util.List;
/**
* @Author: 2019012364 袁梦达 3班
*/
public class Test2 {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("张三丰");
list.add("张无忌");
list.add("张一");
list.add("李四");
list.add("王五");
//输出姓张的且名字是三个字的
//传统方法
for (String s : list) {
if(s.startsWith("张") && s.length() == 3){
System.out.println(s);
}
}
//流的方式
list.stream().filter(s->s.startsWith("张")).filter(s->s.length() == 3).forEach(s-> System.out.println(s));
}
}
和Collection操作不同,Stream操作还有两个基础的特征:
- Pipelining:中间操作都会返回流对象本身,这样多个操作可以串联成一个管道,如同流式风格。这样做可以对操作进行优化,比如延迟执行和短路
- 内部迭代:以前对集合遍历都是通过迭代器或增强for的方式,显式的在级和外部进行迭代,这叫做外部迭代。Stream提供了内部迭代的方式,流可以直接调用遍历方法
获取流
有两种方式:
- 所有的Collection集合都可以通过stream默认方法获取流
- Stream接口的静态方法of可以获取数组对应的流
import org.omg.CORBA.INTERNAL;
import java.util.*;
import java.util.stream.Stream;
/**
* @Author: 2019012364 袁梦达 3班
*/
public class Test2 {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
Stream<String> stream1 = list.stream();
Set<String> set = new HashSet<>();
Stream<String> stream2 = set.stream();
Map<String, String> map = new HashMap<>();
Set<String> set1 = map.keySet();
Stream<String> stream3 = set1.stream();
Set<Map.Entry<String, String>> set2 = map.entrySet();
Stream<Map.Entry<String, String>> stream4 = set2.stream();
Stream<Integer> stream5 = Stream.of(1, 2, 3, 4, 5);
Integer[] a = {1, 2, 3, 4, 5};
Stream<Integer> stream6 = Stream.of(a);
String[] b = {"a", "bb", "ccc"};
Stream<String> stream7 = Stream.of(b);
}
}
常用方法
流模型的方法可以被分成两种:
- 延迟方法:返回值类型仍然是Stream接口自身类型的方法,因此支持链式调用。(除了终结方法,其余方法均为延迟方法)
- 终结方法:返回值类型不再是Stream接口自身类型的方法,因此不再支持类似StringBuilder那样的链式调用。常用的终结方法包括count和forEach方法
逐一处理:forEach
void forEach(Consumer<? super T> action)
该方法接收一个Consumer接口,会将每一个流元素交给该函数处理
是一个终结方法,遍历之后就不能继续调用Stream流中的其他方法
import java.util.stream.Stream;
/**
* @Author: 2019012364 袁梦达 3班
*/
public class Test2 {
public static void main(String[] args) {
Stream<String> stream = Stream.of("张三", "李四", "王五", "赵六");
stream.forEach(s-> System.out.println(s));
}
}
过滤:filter
可以将一个流转换成另一个子集流
Stream filter(Predicate<? super T> predicate)
import java.util.stream.Stream;
/**
* @Author: 2019012364 袁梦达 3班
*/
public class Test2 {
public static void main(String[] args) {
Stream<String> stream = Stream.of("张三", "李四", "王五", "赵六");
Stream<String> stream1 = stream.filter(s-> s.startsWith("张"));
stream1.forEach(s-> System.out.println(s));
}
}
注意:
Stream流属于管道流,只能被消费(使用)一次
第一个Stream流调用完毕方法,数据就会流转到下一个Stream上
而这时第一个Stream流已经使用完毕,就会关闭了
所以第一个Stream流就不能再调用方法了,否则会报错
映射:map
如果需要将流中的元素映射到另一个流上,可以使用map方法
Stream map(Function<? super T, ? extends R> mapper)
import java.util.stream.Stream;
/**
* @Author: 2019012364 袁梦达 3班
*/
public class Test2 {
public static void main(String[] args) {
Stream<String> stream = Stream.of("1", "2", "3", "4");
Stream<Integer> stream1 = stream.map(s -> Integer.parseInt(s));
stream1.forEach(s-> System.out.println(s));
}
}
统计个数:count
和Collection当中的size方法一样,统计流中元素的个数
long count( );
是一个终结方法,不饿能再继续调用Stream流中的其他方法了
取用前几个:limit
可以对流进行截取,只取用前n个
Stream limit(long maxSize);
参数是一个long类型,如果集合当前长度大于参数则进行截取,否则不进行操作
跳过前几个:skip
跳过前几个元素,获取一个截取之后的新流
Stream skip(long n);
如果流的当前长度大于n,则跳过前n个,否则将会得到一个长度为0的空流
组合:concat
将两个流合并为一个流(此方法为静态方法)
static Stream concat(Stream<? extends T> a, Stream<? extends T> b)
方法引用
如果Lambda体中的内容 已经有方法实现了 我们可以使用"方法引用"
/**
* @Author: 2019012364 袁梦达 3班
*/
@FunctionalInterface
public interface Printable {
void print(String s);
}
/**
* @Author: 2019012364 袁梦达 3班
*/
public class DemoPrint {
public static void printString(Printable p){
p.print("helloworld");
}
public static void main(String[] args) {
//Lambda表达式写法
printString(s-> System.out.println(s));
//方法引用写法
printString(System.out :: println);
}
}
通过对象名引用成员方法
前提:对象名是已经存在的,成员方法也是已经存在的
/**
* @Author: 2019012364 袁梦达 3班
*/
@FunctionalInterface
public interface Printable {
public void print(String s);
}
/**
* @Author: 2019012364 袁梦达 3班
*/
public class Method {
public void printTo(String s){
System.out.println(s.toUpperCase());
}
}
/**
* @Author: 2019012364 袁梦达 3班
*/
public class Demo {
public static void printString(Printable p){
p.print("hello");
}
public static void main(String[] args) {
//Printable是函数式接口,所以可以使用Lambda表达式
printString(s-> System.out.println(s.toUpperCase()));
//转成大写的对象Method存在,成员方法printTo也存在,可以使用方法引用
Method method = new Method();
printString(method::printTo);
}
}
通过类名引用静态方法
前提:类已经存在,静态成员方法也已经存在
/**
* @Author: 2019012364 袁梦达 3班
*/
@FunctionalInterface
public interface Calcuable {
public int calAbs(int num);
}
/**
* @Author: 2019012364 袁梦达 3班
*/
public class Demo {
public static int method(int num, Calcuable calcuable){
return calcuable.calAbs(num);
}
public static void main(String[] args) {
//Lambda
int ans = method(-10, n->Math.abs(n));
System.out.println(ans);
//方法引用
int ans2 = method(-10, Math::abs);
System.out.println(ans2);
}
}
通过super引用成员方法
/**
* @Author: 2019012364 袁梦达 3班
*/
public interface Greetable {
public void greet();
}
/**
* @Author: 2019012364 袁梦达 3班
*/
public class Father {
public void sayHello(){
System.out.println("我是父亲!");
}
}
/**
* @Author: 2019012364 袁梦达 3班
*/
public class Son extends Father{
@Override
public void sayHello(){
System.out.println("我是儿子!");
}
public void method(Greetable g){
g.greet();
}
public void show(){
// //第一种
// method(()->{
// new Father().sayHello();
// });
// //第二种
// method(()->{
// super.sayHello();
// });
//第三种
method(super::sayHello);
}
public static void main(String[] args) {
new Son().show();
}
}
通过this引用成员方法
/**
* @Author: 2019012364 袁梦达 3班
*/
public interface Rich {
public void buy();
}
/**
* @Author: 2019012364 袁梦达 3班
*/
public class Husband {
public void buyHouse(){
System.out.println("买房!");
}
public void marry(Rich r){
r.buy();
}
public void happy(){
// //第一种
// marry(()->{
// this.buyHouse();
// });
marry(this::buyHouse);
}
public static void main(String[] args) {
new Husband().happy();
}
}
类的构造器(构造方法)引用
/**
* @Author: 2019012364 袁梦达 3班
*/
public class Person {
private String name;
public Person() {
}
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
/**
* @Author: 2019012364 袁梦达 3班
*/
public interface PersonBuilder {
public Person build(String name);
}
/**
* @Author: 2019012364 袁梦达 3班
*/
class Test {
public static void getName(String name, PersonBuilder p){
Person person = p.build(name);
System.out.println(person.getName());
}
public static void main(String[] args) {
getName("张三", s->new Person(s));
getName("李四", Person::new); //方法引用
}
}
数组的构造器引用
/**
* @Author: 2019012364 袁梦达 3班
*/
public interface ArrayBuilder {
public int[] build(int length);
}
/**
* @Author: 2019012364 袁梦达 3班
*/
class Test {
public static int[] createArray(int length, ArrayBuilder a){
return a.build(length);
}
public static void main(String[] args) {
int[] a = createArray(5, len->new int[len]);
System.out.println(a.length);
int[] b = createArray(6, int[]::new);
System.out.println(b.length);
}
}
反射
反射是框架设计的灵魂
-
框架:半成品软件。可以在框架的基础上进行软件开发,简化编码。
-
反射:将类的各个组成部分封装为其他对象,这就是反射机制
好处:1. 可以在程序运行过程中,操作这些对象。2.可以解耦,提高程序的可扩展性。
获取字节码Class对象的三种方式
-
Class.forName(“全类名”):将字节码文件加载进内存,返回Class对象
多用于配置文件,将类名定义在配置文件中。读取文件,加载类
-
类名.class:通过类名的属性class获取
多用于参数的传递
-
对象.getClass( ):getClass( )方法在Object类中定义
多用于对象的获取字节码的方式
同一个字节码文件在一次程序运行过程中只会被加载一次,不论通过哪一种方式获取的Class对象都是同一个
package reflect;
/**
* @Author: 2019012364 袁梦达 3班
*/
public class Main {
public static void main(String[] args) throws Exception {
//Class.forName("全类名"):将字节码文件加载进内存,返回Class对象
Class cls1 = Class.forName("reflect.Person");
System.out.println(cls1);
//类名.class:通过类名的属性class获取
Class cls2 = Person.class;
System.out.println(cls2);
//对象.getClass( ):getClass( )方法在Object类中定义
Person p = new Person();
Class cls3 = p.getClass();
System.out.println(cls3);
//比较三个对象
System.out.println(cls1 == cls2 && cls2 == cls3);
}
}
Class对象功能
- 获取功能
- 获取成员变量们
- Field[ ] getFields( ) :获取所有public修饰的成员变量
- Field getField(String name):获取指定名称public修饰的成员变量
- Field[ ] getDeclaredFields( ):获取所有成员变量,不考虑修饰符
- Field getDeclaredField(String name)
- 获取构造方法们
- Constructor<?>[ ] getConstructors( )
- Constructor getConstructor(类<?>… parameterTypes)
- Constructor getDeclaredConstructor(类<?>… parameterTypes)
- Constructor<?>[] getDeclaredConstructors()
- 获取成员方法们
- Method [] getMethods( )
- Method getMethod(String name, 类<?>… parameterTypes)
- Method [] getDeclaredMethods( )
- Method getDeclaredMethod(String name, 类<?>… parameterTypes)
- 获取类名 String getName()
- 获取成员变量们
获取Field
Field成员变量操作:
- 设置值:void set(Object obj, Object, value)
- 获取值:get(Object obj)
- 忽略访问权限修饰符的安全检查:setAccessible(true):暴力反射
package reflect;
/**
* @Author: 2019012364 袁梦达 3班
*/
public class Person {
private String name;
private int age;
public int a;
public int b;
protected int c;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
package reflect;
import java.lang.reflect.Field;
/**
* @Author: 2019012364 袁梦达 3班
*/
public class Main {
public static void main(String[] args) throws Exception {
Class personClass = Person.class;
Field[] f1 = personClass.getFields();
for (Field field : f1) {
System.out.println(field);
}
System.out.println("======================");
Field[] f2 = personClass.getDeclaredFields();
for (Field field : f2) {
System.out.println(field);
}
Field f3 = personClass.getDeclaredField("name");
Person p = new Person();
f3.setAccessible(true); //暴力反射
Object o = f3.get(p);
System.out.println(o);
f3.set(p, "张三");
System.out.println(f3.get(p));
}
}
获取Constructor
操作:创建对象
- T newInstance(Object… initargs)
- 如果使用空参数构造方法创建对象,操作可以简化:Class对象的newInstance方法
package reflect;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
/**
* @Author: 2019012364 袁梦达 3班
*/
public class Main {
public static void main(String[] args) throws Exception {
Class personClass = Person.class;
Constructor constructor = personClass.getConstructor(String.class, int.class);
System.out.println(constructor);
Object obj = constructor.newInstance("张三", 18);
System.out.println(obj);
Object obj1 = personClass.newInstance(); //空参构造
System.out.println(obj1);
}
}
获取Method
操作:执行方法:Object invoke (Object obj, Object… args)
获取方法名:getName( )
package reflect;
/**
* @Author: 2019012364 袁梦达 3班
*/
public class Person {
private String name;
private int age;
public int a;
public int b;
protected int c;
public void eat(){
System.out.println("吃");
}
public void eat(String food){
System.out.println("吃" + food);
}
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
package reflect;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
/**
* @Author: 2019012364 袁梦达 3班
*/
public class Main {
public static void main(String[] args) throws Exception {
Class personClass = Person.class;
Method method1 = personClass.getMethod("eat");
Person p = new Person();
method1.invoke(p);
Method method2 = personClass.getMethod("eat", String.class);
method2.invoke(p, "饭");
}
}
注解
概念:说明程序的。
和注释的区别:注释是给程序员看的,注解是给计算机看的
作用:
- 编写文档:生成doc文档
- 代码分析
- 编译检查:Override
JDK内置注解
- @Override:检测被该注解标注的方法是否继承自父类(接口)
- @Deprecated:将该注解标注的内容已过时
- @SuppressWarnings:压制警告,@SuppressWarnings(“all”)可以压制所有警告
自定义注解
格式
元注解
public @interface 注解名称( ){ }
本质
本质就是一个接口,默认继承Annotation接口
属性
接口中的抽象方法
-
要求:
-
属性的返回值类型:只能是基本数据类型,String, 枚举,注解,及以上类型的数组
-
定义了属性,在使用时要给属性赋值
如果定义属性时使用default关键字给属性默认初始化值,则使用注解时,可以不进行属性的赋值
如果只有一个属性需要赋值,并且属性的名称时value,则value可以省略,直接定义值即可
-
元注解
用于描述注解的注解
-
@Target:描述注解能够作用的位置
ElementType取值:
- TYPE:可以作用于类上
- METHOD:可以作用于方法上
- FIELD:可以作用于成员变量上
-
@Retention:描述注解被保留的阶段
@Retention(RetentionPolicy.RUNTIME):当前被描述的注解,会保留到class字节码文件中,并被JVM读取到
-
@Documented:描述注解是否被抽取到api文档中
-
@Inherited:描述注解是否被子类继承