IDEA操作
IDEA导入Eclipese文件
2.选择文件
3.选择Eclipse
无脑下一步
4.更改项目结构选择jdk
导出可运行Jar包
打开项目结构,选择以下
选择主类
构建
构建
运行jar包
导入JAR包
之后选择jar包路径即可
数据类型
面向对象编程
类的五大成员:方法,属性,代码块,内部类,构造器
面向对象编程—多态
向上转型和向下转型
//向上转型
class A{
}
class B extends A{
}
public class Example{
public static void main(String[] args){
A a = new B();
//向上转型 如果A调用方法,这个方法要先从B中找
//等号前面为引用(编译),等号后面为对象(运行)
//调用属性看引用,调用方法看对象
}
}
//向下转型
class A{
}
class B extends A{
}
public class Example{
public static void main(String[] args){
A a = new B();
B b = (B) a; //父类的引用必须指向当前目标类型的对象
//即引用a必须指向B的对象
}
}
动态绑定机制(非常重要)
调用该对象方法时,该方法会和该对象的内存地址/运行类型绑定
调用对象属性时,没有动态绑定机制
class A{
public int i = 10;
public int sum(){
return getI() + 10;
}
public int sum1(){
return i + 10;
}
public int getI(){
return i;
}
}
class B extends A{
public int i = 20;
public int sum(){
return i + 20;
}
public int sum1(){
return i + 10;
}
public int getI(){
return i;
}
}
public class Example{
public static void main(String[] args){
A a = new B();//向上转型
System.out.println(a.sum()); //40
System.out.println(a.sum1()); //30
}
}
class A{
public int i = 10;
public int sum(){
return getI() + 10;
}
public int sum1(){
return i + 10;
}
public int getI(){
return i;
}
}
class B extends A{
public int i = 20;
// public int sum(){
// return i + 20;
// }
// public int sum1(){
// return i + 10;
// }
public int getI(){
return i;
}
}
public class Example{
public static void main(String[] args){
A a = new B();//向上转型
System.out.println(a.sum()); //40-》30
System.out.println(a.sum1()); //30-》20
//a动态绑定为B, A中sum方法返回getI()+10,getI()是B中的getI()
//A中sum1()方法返回i+10,而这个i没有动态绑定机制,就是A中的i
}
}
多态应用—多态数组
数组的定义类型为父类类型,里面保存的实际元素类型为子类类型
class Person{
private String name = "李华";
private int age;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String say() {
return "我叫"+name+"年龄为"+age;
}
}
class Student extends Person{
private int id;
private int score;
public Student(String name,int age, int id, int score) {
// TODO Auto-generated constructor stub
super(name, age);
this.id = id;
this.score = score;
}
public String say() {
return super.say()+"学号"+id+"成绩"+score;
}
}
class Teacher extends Person{
int id;
int salary;
public Teacher(String name,int age, int id, int salary) {
// TODO Auto-generated constructor stub
super(name, age);
this.id = id;
this.salary = salary;
}
public String say() {
return super.say()+"编号"+id+"薪水"+salary;
}
}
/* 运行结果:
我叫smith年龄为18
我叫Black年龄为30编号2000薪水25000
我叫Green年龄为50编号1200薪水20000
我叫liu年龄为15学号1成绩60
我叫liuxiao年龄为14学号2成绩89
*/
//如想用特有属性则在for循环中使用向下转型即:
for(int i = 0; i<persons.length;i++) {
System.out.println(persons[i].say());
if(persons[i] instanceof Student) {
Student student = (Student)persons[i];
student.study();
// ((Student)persons[i]).study();
}else if(persons[i] instanceof Teacher) {
Teacher teacher = (Teacher)persons[i];
teacher.teach();
}
}
多态参数
方法定义的形参为父类,传的实参可以为子类
public class Example{
public static void main(String[] args) {
Worker lilan = new Worker(3000, "lilan");
Manager liqing = new Manager(4500, "liqing", 2000);
Example example = new Example();
example.showEmpAnnual(lilan);
example.showEmpAnnual(liqing);
example.showWork(lilan);
example.showWork(liqing);
}
public void showEmpAnnual(Employee e) {
//形参e相当于 Employee的引用,实际指向的是实参的对象
System.out.println(e.getAnnual());
}
public void showWork(Employee e) {
if(e instanceof Worker){
((Worker)e).work();//向下转型
}else if(e instanceof Manager) {
((Manager)e).manage();//向下转型
}
}
}
/*
36000
56000
普通员工lilanis working
经理liqingis managing
object类的详解
==运算符
1.==是一个比较运算符
可以判断基本类型,也可判断引用类型
判断基本类型时,判断值是否相等
判断引用类型时,判断的是地址是否相等
class Example{
public static void main(String[] args){
A a = new A();
A b = a;
A c = b;
B bobj = a;//向上转型
System.out.println(a==b)//true 比较的是地址
System.out.println(b==c)//true
System.out.println(bobj==a)//true
}
}
class B {}
class A extends A{}
equals
equals只能判断引用类型
object子类往往重写equals方法用来判断值是否相等
hashCode
1.提高具有哈希结构的容器的效率
2.两个引用指向相同的对象哈希值是一样的
3.哈希值主要是根据地址号来处理的,不能完全将哈希值等价于地址
toString
返回该对象的的字符串表示,默认返回:全类名+@+哈希值的十六进制(全类名:包名+类名)
当直接输出一个对象时,toString方法会被默认的调用
package 方法重写;
public class Example{
public static void main(String[] args) {
Monster monster = new Monster("小狐狸", 1000);
System.out.println(monster);
}
}
class Monster{
private String name;
private int salary;
public Monster(String name, int salary) {
// TODO Auto-generated constructor stub
this.name = name;
this.salary = salary;
}
}
/*方法重写.Monster@27bc2616
经常重写该方法使其输出对象
class Monster{
private String name;
private int salary;
public Monster(String name, int salary) {
// TODO Auto-generated constructor stub
this.name = name;
this.salary = salary;
}
public String toString() {
return "Monster"+name+salary;
}
}
finalize
1.当垃圾回收器确定不存在该对象的更多引用时,由对象被回收时使用
- 对象被回收前,系统自动调用该方法,子类可以重写该方法,实现自己的业务(释放资源,数据库连接,或者打开文件
- 垃圾回收机制的调用由系统决定,也可以使用System.gc()手动回收
main方法语法
- main方法是虚拟机调用
- java虚拟机需要调用类的main(方法,所以该方法的访问权限必须是public
- java虚拟机在执行main()方法时不必创建对象,所以该方法必须是static
- 该方法接收String类型的数组参数,该数组中保存执行java命令时传递给所运行的类的参数
- java执行的程序 参数1 参数2 参数3
特别提示:
- main()方法中,我们可以直接调用main()方法所在类的静态方法和静态成员
- 不能调用非静态方法和非静态成员,如果需要调用则需要创建对象
动态传参:
在下面图片程序实参中传入参数
代码块
介绍
相当于对构造器的补充,可以做初始化操作,如果多个构造器有重复的语句,可以抽取到初始化块中,提高代码重用性
代码块优先于构造器
public class A {
public static void main(String[] args) {
B b = new B("小梦");
B b1 = new B("小红",14);
B b2 = new B("小兰",20,1000);
}
}
class B{
String name;
int age;
int salary;
// 这就是代码块
{
System.out.println("我是人");
System.out.println("我在打工");
System.out.println("我工资只够生活");
}
public B(String name) {
this.name = name;
}
public B(String name, int age) {
this.name = name;
this.age = age;
}
public B(String name, int age, int salary) {
this.name = name;
this.age = age;
this.salary = salary;
}
}
/*
我是人
我在打工
我工资只够生活
我是人
我在打工
我工资只够生活
我是人
我在打工
我工资只够生活
细节
-
static代码块也叫静态代码块,作用是对类进行初始化,随着类加载而执行,并且只会执行一次,而非静态每创建一个对象就执行一次
-
类什么时候加载: 创建对象实例时 创建子类对象实例时,父类也会被加载 使用类静态成员时
-
static{ System.out.println("静态代码块"); }
-
普通代码块在使用类静态成员时不会被执行
-
创建一个对象时,在一个类调用顺序是:
1)调用静态代码块和静态属性初始化(如果有多个静态代码块和静态变量初始化,则按定义顺序调用
2)调用普通代码块和普通属性初始化(如果有多个普通代码块和普通变量初始化,则按定义顺序调用
3)调用构造方法
-
构造器的最前面其实隐含了super()和调用普通代码块
public class A { public static void main(String[] args) { E e = new E(); } } class D{ D(){ System.out.println("D"); } } class E extends D{ { System.out.println("EEE"); } E(){ //隐藏super() //隐藏代码块 System.out.println("E"); } } /* D EEE E
-
创建一个子类对象时(继承关系),他们的静态代码块,静态属性初始化,普通代码块,普通属性初始化,构造方法调用顺序:
1)父类的静态代码块和静态属性(优先级一样按定义顺序执行
2)子类的静态代码块和静态属性(优先级一样按定义顺序执行
3)父类的普通代码块和普通属性(优先级一样按定义顺序执行
4)父类的构造方法
5)子类的普通代码块和普通属性(优先级一样按定义顺序执行
6)子类的构造方法
public class A {
public static void main(String[] args) {
CCC c = new CCC();
}
}
class BBB{
private static int n1 = getN1();
private int n2 = getN2();
{
System.out.println("BBB普通代码块");
}
static {
System.out.println("BBB静态代码块");
}
static int getN1(){
System.out.println("BBB静态属性");
return 1;
}
int getN2(){
System.out.println("BBB普通属性");
return 2;
}
BBB(){
System.out.println("BBB构造方法");
}
}
class CCC extends BBB{
private static int n01 = getN01();
private int n02 = getN02();
{
System.out.println("CCC普通代码块");
}
static {
System.out.println("CCC静态代码块");
}
static int getN01(){
System.out.println("CCC静态属性");
return 1;
}
int getN02(){
System.out.println("CCC普通属性");
return 2;
}
CCC(){
//隐藏
//super();
//普通代码块
System.out.println("CCC构造方法");
}
}
/*
BBB静态属性
BBB静态代码块
CCC静态属性
CCC静态代码块
BBB普通属性
BBB普通代码块
BBB构造方法
CCC普通属性
CCC普通代码块
CCC构造方法
- 静态代码块只能使用静态成员,普通代码块能使用任意成员
单例设计模式
饿汉式
-
构造器私有化(防止用户直接new
-
类的内部创建对象,该对象是静态的
-
向外暴露一个静态的公共方法,getInstance
-
只要类加载,对象就被创建。
-
还没有用到对象,就使用(饿汉式原因
-
可能造成创建了对象但是没有使用
public class A { public static void main(String[] args) { // 通过方法获得对象 GirlFriend gif = GirlFriend.getInstance(); System.out.println(gif.toString()); // GirlFriend gif2 = GirlFriend.getInstance(); System.out.println(gif2.toString()); System.out.println(gif==gif2); } } //只能有一个女朋友 class GirlFriend{ private String name; // 为了在静态方法中,返回gif对象,需要将gif设置为静态 private static GirlFriend gif = new GirlFriend("小红红"); private GirlFriend(String name){ this.name = name; } // 不需要创建对象 public static GirlFriend getInstance(){ return gif; } @Override public String toString() { return "GirlFriend{" + "name='" + name + '\'' + '}'; } } /* GirlFriend{name='小红红'} GirlFriend{name='小红红'} true
懒汉式
- 构造器私有化
- 定义一个static对象(不初始化
- 提供一个public的static方法,返回gif对象
- 只有当用户使用getInstance才创建gif对象,后面再次调用时,直接返回gif对象
public class A {
public static void main(String[] args) {
// 通过方法获得对象
GirlFriend gif = GirlFriend.getInstance();
System.out.println(gif.toString());
//
GirlFriend gif2 = GirlFriend.getInstance();
System.out.println(gif2.toString());
System.out.println(gif==gif2);
}
}
//只能有一个女朋友
class GirlFriend{
private String name;
// 为了在静态方法中,返回gif对象,需要将gif设置为静态
private static GirlFriend gif = null;
private GirlFriend(String name){
this.name = name;
}
// 不需要创建对象
public static GirlFriend getInstance(){
if(gif == null){
gif = new GirlFriend("小红红");
}
return gif;
}
@Override
public String toString() {
return "GirlFriend{" +
"name='" + name + '\'' +
'}';
}
}
/*
GirlFriend{name='小红红'}
GirlFriend{name='小红红'}
true
对比
- 对象创建时机不同
- 懒汉式:三个线程同时进入getInstance()方法时,三个线程同时创建对象,线程不安全
- 饿汉式存在浪费资源问题,懒汉式不存在
内部类
局部内部类(有类名
- 局部内部类定义在外部类的局部位置,通常在方法或代码块
- 可以访问外部类的所有成员,包含私有成员
- 不能添加访问修饰符,但可以使用final修饰(可以在外部类的局部位置被继承
- 作用域:仅仅定义它的方法或代码块中,相当于局部变量
- 局部内部类可以直接访问外部类的成员
- 外部类可以创建内部类的对象,然后调用方法
- 如果外部类和内部类的成员重名,遵循就近原则,如果想访问外部类的成员,使用外部类名.this.成员
package 局部内部类;
public class Example {
public static void main(String[] args) {
Outer outer = new Outer();
outer.m1();
System.out.println("Outer的hashcod"+outer);
}
}
class Outer{
private int n = 100;
private void m2() {
System.out.println("m2");
}
public void m1() {
class Inner{
public void f1() {
System.out.println("f1");
m2();
System.out.println("内部类的n"+n+"外部类的n"+Outer.this.n);
System.out.println("内部类的Outer的hashcode"+Outer.this);
}
}
Inner inner = new Inner();
inner.f1();
}
}
/*
f1
m2
内部类的n400外部类的n100
内部类的Outer的hashcode局部内部类.Outer@24d46ca6
Outer的hashcod局部内部类.Outer@24d46ca6
匿名内部类(没有类名,!!
-
本质是类,该类没有名字,匿名内部类是定义在外部类的局部位置,比如方法中,而且没有类名(系统自动分配
-
基本语法
new 类或接口(参数列表){ 类体 }
-
jdk底层在创建内部类时,立马就创建了实例返回
-
匿名内部类使用一次就不能再使用了
-
可以使用外部类的所有属性
-
不能添加访问修饰符
-
作用域:方法或者代码块中
-
外部其他类不能访问匿名内部类
-
如果外部类和内部类的成员重名,遵循就近原则,如果想访问外部类的成员,使用外部类名.this.成员
package 匿名内部类; public class AnonymousInner { public static void main(String[] args) { Outer outer = new Outer(); outer.m1(); } } class Outer{ private int n = 100; public void m1() { // tiger编译类型是A接口 // 运行类型就是匿名内部类 // 底层分配类名 ....$... 只能用一次,但tiger可以用很多次 A tiger = new A(){ public void cry() { System.out.println("Tiger叫"); }; }; System.out.println(tiger.getClass()); // father编译类型Father // 运行类型 // 底层创建匿名内部类 class Outer$2 extends Father{} Father father = new Father("老二") { // 匿名内部类重写test方法 @Override public void test() { // TODO Auto-generated method stub super.test(); System.out.println("test重写"); } }; System.out.println(father.getClass()); father.test(); } } interface A{ void cry(); } class Father{ private String name; public Father(String name) { this.name = name; } public void test() { System.out.println("test"); } } /* class 匿名内部类.Outer$1 class 匿名内部类.Outer$2 test test重写
package 匿名内部类; //直接当作参数传递 public class Example2 { public static void main(String[] args) { f1(new IL() { @Override public void show() { // TODO Auto-generated method stub System.out.println("这是一副名画"); } }); } static public void f1(IL il) { il.show(); } } interface IL{ void show(); } /* 这是一幅名画
成员内部类
-
定义在外部类的成员位置
-
可以使用public protected private修饰
-
作用域为整个类体,在外部类的成员方法中先创建内部类对象再调用方法
-
可以直接访问外部类的属性和方法
-
外部类只能创建对象再访问内部类的属性和方法
-
外部无关类访问内部类
Outer outer = new Outer(); Inner inner = Outer.new Inner(); Outer.Inner inner = Outer.new Inner();
-
如果外部类和内部类的成员重名,遵循就近原则,如果想访问外部类的成员,使用外部类名.this.成员
静态内部类
-
放在外部类的成员位置
-
使用static修饰
-
可以直接访问外部类的所有静态成员,不能访问非静态成员
-
可以添加任意修饰符
-
作用域:整个类
-
外部类访问静态内部类要创建对象在调用方法
-
外部其他类访问静态内部类
Outer.Inner inner =new Outer.Inner();
-
如果外部类和内部类的成员重名,遵循就近原则,如果想访问外部类的成员,使用外部类名.成员(不用加this,因为本身是静态的)
常用实用类
1. 包装类
八大包装类
黄色父类为Number,黑色部分父类为object
装箱拆箱
- jdk5之后为自动拆箱装箱
- int-》Integer 装箱 integer-》int 拆箱
- 自动装箱底层还是用的ValueIOf()方法
package 装箱拆箱;
public class Example {
public static void main(String[] args) {
// 手动装箱
int n = 10;
Integer integer = new Integer(n);
Integer integer2 = Integer.valueOf(n);
// 手动拆箱
int i = integer.intValue();
// jdk5后自动装箱拆箱,底层使用还是Integer.valueOf()方法
int n2 = 200;
Integer integer3 = n2;
int n3 = integer3;
}
}
- ==注意!!!==三元运算符要看作一个整体
Object obj1 = ture?new Integer(1):new Double(2.0);
System.out.print(obj1)
//输出结果为1.0,三元运算符看成一个整体,会提高优先级
Object obj2;
if(true)
obj2 = new Integer(1);
else
obj2 = new Double(2.0);
System.out.print(obj2);
//输出1
包装类方法
转换
public class Example2 {
public static void main(String[] args) {
// 包装类Integer-》String
Integer integer = 100; //自动装箱
// 1
String str1 = integer+"";
// 2
String str2 = integer.toString();
// 3
String str3 = String.valueOf(integer);
// String->转Integer
String str4 = "1234";
// 1
Integer integer2 = Integer.parseInt(str4);
// 2
Integer integer3 = new Integer(str4);
}
}
2. Integer
package 装箱拆箱;
public class Example3 {
public static void main(String[] args) {
Integer integer=new Integer(1);
Integer jInteger = new Integer(1);
System.out.println(integer == jInteger);//false
//在-128 到 127 之间,直接从数组返回
Integer mInteger=1;
Integer nInteger = 1;
System.out.println(mInteger == nInteger);//ture
//超过范围,返回一个new Integer();
Integer aInteger = 128;
Integer bInteger = 128;
System.out.println(aInteger == bInteger);//false
//只有有基本属性,判断的是值是否相等
Integer i11 = 127;
int i12 = 127;
System.out.println(i11==i12)//true
}
}
valueof源码
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high) //low-128, high127
return IntegerCache.cache[i + (-IntegerCache.low)];
//IntegerCache.cache[i + (-IntegerCache.low)] 是一个已经定义好的数组
return new Integer(i);
}
3. String类
-
字符串常量是用引号阔气的字符串序列
-
字符使用unicode编码,一个字符(字母或者汉字)都占两个字节
-
实现Serializable接口(可以串行化,在网络上传输)和Comparable接口(可以进行比较)
-
是一个final类,不能被继承
-
底层是 char value[] 数组,value 是一个final类型,不可以修改(地址不能修改)
final char[] value = {'j','a','v','a'}; value[0] = 'H'; //可以 char[] v2 = {'t','o','m'}; value = v2; //不可以修改地址
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DCjurjfl-1671179039718)(E:\新建文件夹\java\image\类\String底层数组.png)]
构造字符串对象
- 先从常量池查看是否有“java”数据空间,如果有,直接指向,如果没有则重新创建然后指向,s最终指向的是常量池的空间地址
- 现在堆中创建空间,维护一个value数组,指向常量池的java空间,如果常量池没有,重新创建,如果有直接通过value指向,最终指向的是堆中的空间地址[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DL7PdoLu-1671179039719)(E:\新建文件夹\java\image\类\创建String.png)]
- 当调用intern()方法时,如果池已经包含一个等于此String对象的字符串(用equals()方法确定)则返回池中的字符串,否则,将此String对象添加到池中,并返回此String对象的引用, b.intern()方法最终返回的是常量池的地址(对象)
//1.
String s= "java"
//2.
String s2 = new String("java");
//3.
char a[] = {'J','a',v','a'};
string s = new String(a);
//4 String(char a[], int startIndex, int count)
char a[] = {'1','2','3','4','5','6','7'};
String s = new String(a,2, 4) //s = '3456'
例题
例题1:
String a = "java"
String b = new String("java");
a.equals(b) //True
a == b //False a指向方法区常量池,b指向堆
a == b.intern() //True
b == b.intern() //False b指向堆,b.intern()指向常量池
例题2:
Person p1 = new Person();
Person p2 = new Person();
p1.name = 'java';
p1.name = 'java';
p1.name.equals(p2.name); //true
p1.name == p2.name; //true
p1.name == "java"; //true
String s1 = new String("abc");
String s2 = new String("abc");
s1 == s2//false s1和s2指向堆中的value,s1和s2value地址不同,为false
例3
//1
String a = "java";
a = "python";
//创建了两个对象
//2
String b = "hello" + "abd";
//创建了一个对象,编译器优化:直接生成helloabd
//3
String a = "hello";
String b = "abd";
String c = a+b;
//先创建一个StringBuilder sb = new StringBuilder()
//执行sb.append()方法, 第一步sb.append("hello") 第二步sb.append("abd")
//调用sb.toString()方法 返回一个new String()
//最后其实是c指向堆中的对象(String) value[] -》 池中"helloabd"
//一共三个对象
例4
public class Example {
String str = new String("java");
final char[] ch = {'p','t','h'};
public void change(String str, char ch[]) {
str = "c++";
ch[0] = 'h';
}
public static void main(String[] args) {
Example example = new Example();
example.change(example.str, example.ch);
System.out.println(example.str+" and ");
System.out.println(example.ch);//print函数的重载,print(char[] a)会直接输出char数组内容,而其他类型1数组则会输出完整类名+哈希值的16位表示
}
}
/*
java and
hth
常用方法
//1. public int length()
String a = "abc";
a.length() = 3;
//2. public boolean equals(String s)
a.equals('abc') // true
//3.public boolean startsWith(String s)
// public boolean endWith(String s) 判断字符串后缀是否以s结尾
//4.public int compareTo(String s)
//按字典序与参数s比较大小, 与s相同返回0,大于返回正值,小于返回负值
String str = 'abcd';
str.compareTo("boy") //负值
//5 public boolean contains(String s)是否包含
//6 public int indexOf(String s)
// 字符串位置索引从0开始,判断s首次出现的位置,如果没有返回-1
//public int indexOf(String s, int startpoint) 从startpoint开始
//7 public String substring(int startpoint) 截取startpoint之后的字符串
// substring(int start, int end) 截取start到end-1
//8 public String trim() 去掉s前后空格
//9 equalslgnoreCase() 忽略大小写判断内容
//10 charAt获取某索引处的字符,注意不能使用Str[index]方式
//11 toUpperCase 转换成大写
//12 toLowerCase 转换成小写
//13 s1.replace(源原字符串, 替换后的字符串) 替换字符串内容,返回的结果才是替换过的,对s1没有任何影响
//split() 分割字符串,注意转义字符
字符串和基本数据相互转换
将由数字组成的字符从转化成数字
int x;
String s = "123";
x = Integer.parseInt(s);
使用java.lang包中的类Byte,Short,Long,Float,Double类调用相应的类方法
public static byte parseByte(String s ) throws NumberFormatException;
public static short parseShort(String s ) throws NumberFormatException;
public static long parseLong(String s ) throws NumberFormatException;
public static float parseFloat(String s ) throws NumberFormatException;
public static double parseDouble(String s ) throws NumberFormatException;
用String类中的方法将数值转换为字符串
public static String valueOf(int n);
public static String valueOf(byte n);
public static String valueOf(double n);
public static String valueOf(long n);
public static String valueOf(double n);
例子
public class transform {
public static void main(String[] args) {
double aver = 0, sum = 0,item = 0;
boolean computable = true;
String arg[] = new String[5];
Scanner scanner = new Scanner(System.in);
for(int i = 0; i < 5; i++) {
arg[i] = scanner.nextLine();
}
for(String s:arg) {
try{
item = Double.parseDouble(s);
sum = sum + item;
}
catch (NumberFormatException e) {
// TODO: handle exception
System.out.println("您输入了非数字字符串"+e);
computable = false;
}
}
if(computable) {
System.out.println(sum);
}
}
}
对象的字符串表示
Object有一个public String toString() 方法,一个对象调用该方法可以获得该对象字符串表示: 创建对象的类的名字@对象的引用的字符串表示
字符串与字符,字节数组
public class Example9_6 {
public static void main(String[] args) {
char[] a,c;
String string = "2009年10月1日是国庆60周年";
a = new char[2];
string.getChars(11, 13, a, 0); //国庆
/*
getChars(int start, int end, char[] c, int offset)
从字符串start开始end结束 复制到数组c从offset开始
*/
System.out.println(a);
c = "十一长假期间,学校都放假了".toCharArray();
for(char ch: c) {
System.out.print(ch);
}
}
}
public byte[] getBytes(String charsetName)得到字符串的字节数组, 汉字用两个单元
byte d = "java你好"。getBytes();
// d[0] = 'j' d[4]和d[5]储存"你" d的长度为8
正则表达式
. | 可以匹配任何字符 |
---|---|
\d | 匹配 0~9 的所有数字 |
\D | 匹配非数字 |
\s | 匹配所有的空白字符,包括空格、制表符、回车符、换页符、换行符等 |
\S | 匹配所有的非空白字符 |
\w | 匹配所有的单词字符,包括 0~9 所有数字、26 个英文字母和下画线_ |
\W | 匹配所有的非单词字符 |
\\p{Punct} | 标点符号 |
X? | X表达式出现零次或一次 |
---|---|
X* | X表达式出现零次或多次 |
X+ | X表达式出现一次或多次 |
X{n} | X表达式出现 n 次 |
X{n,} | X表达式最少出现 n 次 |
X{n,m} | X表达式最少出现 n 次,最多出现 m 次 |
表示枚举 | 例如[abc] 表示 a、b、c 其中任意一个字符;[gz] 表示 g、z 其中任意一个字符 |
---|---|
表示范围:- | 例如[a-f] 表示 a~f 范围内的任意字符;[\\u0041-\\u0056] 表示十六进制字符 \u0041 到 \u0056 范围的字符。范围可以和枚举结合使用,如[a-cx-z] ,表示 ac、xz 范围内的任意字符 |
表示求否:^ | 例如[^abc] 表示非 a、b、c 的任意字符;[^a-f] 表示不是 a~f 范围内的任意字符 |
表示“与”运算:&& | 例如 [a-z&&[def]] 是 a~z 和 [def] 的交集,表示 d、ef[a-z&&^bc]] 是 a~z 范围内的所有字符,除 b 和 c 之外[ad-z] [a-z&&[m-p]] 是 a~z 范围内的所有字符,除 m~p 范围之外的字符 |
表示“并”运算 | 并运算与前面的枚举类似。例如[a-d[m-p]] 表示 [a-dm-p] |
字符串替换
public String replaceAll(String regex, String replacement)
字符串分解
public String [] split(String regex)
public class Example {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System/.out.println("一行文本");
String string = scanner.nextLine();
String regex = "[\\s\\p{Punct}\\d]+";
String words[] = string.split(regex);
for(String i:words) {
System.out.println(i);
}
System.out.println(words.length);
}
}
/*
4. StringBuffer类
结构
- 代表可变的字符序列,可以对字符串内容进行增删
- 很多方法和String一样,但是可变长度
- 是一个容器
- StringBuffer直接父类为AbstractStringBiulder,实现了Serializable(串行化)
- 在父类AbstractStringBiulder中,有属性char[] value,不是final,存放字符串内容,因此存放在堆中
- StringBuffer是一个final类
String和StringBuffer
- String保存的是字符串常量,里面的值不能更改,每次更新就是更改地址,效率较低(private final char value[],放在常量池
- StringBuufer保存到是字符粗变量,里面的值可以更改,实际上可以更新内容,不用每次更新地址(需要扩容时才更新),效率较高(char[] value 放在堆
构造器
-
new StringBuffer(); 构造一个不带字符串的字符串缓冲区,初始容量为16
-
new StringBuffer(100); 指定char[]的大小
-
new StringBuffer(“hello”); char[]数组大小就是字符串长度+16
-
String-》StringBuffer
String str = "java"; StringBuffer stringBuffer = new StringBuffer(str) //返回的是StringBuffer对象,不改变str StringBuffer stringBuffer1 = new StringBuffer(); stringBuffer1.append(str);
-
StringBuffer->String
StringBuffer stringBuffer = new StringBuffer("java"); String s = stringBuffer.toString(); String s1 = new String(stringBuffer);
方法
StringBuffer append();
delete(start, end)
replace(start,end,string)
indexOf()
insert(index, str)
length
例题
String str = null;
StringBuffer stringBuffer1 = new StringBuffer();
stringBuffer1.append(str);
//输出null,stringBuffer1.length() = 4
//看源码,调用父类AbstractStringBiulder的append方法
StringBuffer stringBuffer1 = new StringBuffer(str);
//super(str.length+16)
//抛出空指针异常
5. StringBuilder
-
一个可变的字符串序列,此类提供一个StringBuffer兼容的API,但不保证同步(不是线程安全),此类被设计用作StringBuffer的一个简易替换,用在字符串缓冲区被单个线程使用的时候,如果可能,建议优先采用该类,因为在大多数实现中,比StringBuffer快
-
重载append和insert(最主要的方法
-
父类也是AbstractStringBuilder
-
其方法没有sychornized
String,StirngBuffer,StringBiulder
String:不可变字符序列,效率低,复用率高
StringBuffer : 可变序列,效率较高(增删)线程安全
StringBuilder:可变序列,效率最高,线程不安全
如果对Stirng做大量修改,不能使用String(会产生大量副本)
如果存在大量修改,一般使用StringBuffer或StringBuilder
如果存在大量修改,多线程,用StringBuffer
如果存在大量修改,单线程,用StringBuilder
很少修改,被多个对象引用,用String,比如配置信息
package String类;
public class compare {
public static void main(String[] args) {
String textString = "";
StringBuffer stringBuffer = new StringBuffer();
StringBuilder stringBuilder = new StringBuilder();
long starttime = System.currentTimeMillis();
for(int i = 0; i<100000;i++) {
stringBuffer.append(String.valueOf(i));
}
long endtime = System.currentTimeMillis();
System.out.println("StringBuffer运行时间"+(endtime-starttime));
long starttime1 = System.currentTimeMillis();
for(int i = 0; i<100000;i++) {
stringBuilder.append(String.valueOf(i));
}
long endtime1 = System.currentTimeMillis();
System.out.println("StringBuilder运行时间"+(endtime1-starttime1));
long starttime2 = System.currentTimeMillis();
for(int i = 0; i<100000;i++) {
textString = textString + i;
}
long endtime2 = System.currentTimeMillis();
System.out.println("String运行时间"+(endtime2-starttime2));
}
}
/*
StringBuffer运行时间14
StringBuilder运行时间4
String运行时间4567
6. StringTokenizer类
常用构造方法
StringTokenizer(String s)
使用默认的分隔符标记,即空格、换行符、回车符、tab符
StringTokenizer(String s, String delim)
使用delim作为分割标记
常用方法
countTokens() 得到分析其中计数变量的值
nextToken() 在字符串中获得下一个语言符号, 计数变量的值就会自动减一
hasMoreTokens() 通常用while循环来逐个获取语言符号,为了控制循环使用该方法
import java.util.StringTokenizer;
public class Example {
public static void main(String[] args) {
String s = "you are welcome(thank you), nice to meet you";
StringTokenizer fenxi = new StringTokenizer(s,"() ,");
System.out.println(fenxi.countTokens()); //9
while(fenxi.hasMoreTokens()) {
System.out.print(fenxi.nextToken()+" ");
}
//you are welcome thank you nice to meet you
System.out.println("\n"+fenxi.countTokens()); //0
}
}
7.Arrays类
Arrays.toString(arr); //显示数组
sort(arr)
//sort()可以重载,sort(arr, comparator<>) 指定排序规则
binarySearch (arr, index)
//通过二分法查找
copyOf(arr, arr.length)
//从arr数组中,拷贝arr.length个元素到newArr中
//如果拷贝长度>arr.length就在新数组后面加null
//如果拷贝长度<0就抛出异常
//底层使用的是System.array.copy()
fill(arr, value)
//数组填充,使用value填充arr
equals()
//比较两个数组元素内容是否完全一致
asList
//将一组值转换成list集合
Scanner类
用来解析字符串或者文件;
hasNextline判断是否有下一行
hasNext判断是否有下一个单词
Date类
构造Date对象
1.使用无参构造方法获取本地实际
Date date = new Date()
2.使用带参数的构造方法
Date date = new Date(1000)
计算机系统公元时间是1970年1月1日0时, 北京时间8时
默认表示:星期、月、日、小时、分、秒、年
日期格式化
使用DateFormat子类SimpleDateFormat来实现日期格式化
SimpleDateFormat SDF = new SimpleDateFormat(pattern)
pattern是由普通字符和一些称作格式符组成的字符序列
( d ) | 将日显示为不带前导零的数字(如 1) 。 |
---|---|
( dd ) | 将日显示为带前导零的数字(如 01)。 |
( ddd ) | 将日显示为英文缩写形式(如 Sun)。 |
( dddd ) | 将日显示为英文全名(如 Sunday)。 |
MM | 将月份显示为带前导零的数字(如 01/12/05)。 |
---|---|
MMM | 将月份显示为英文缩写形式(如 Jan)。 |
MMMM | 将月份显示为完整月份名(如 January)。 |
hh | 使用12小时制将小时显示为带前导零的数字(如 01:15:15 PM)。 |
---|---|
HH | 使用24小时制将小时显示为带前导零的数字(如 01:15:15)。 |
mm | 将分钟显示为带前导零的数字(如 12:01:15)。 |
---|---|
ss | 将秒显示为带前导零的数字(如 12:15:05)。 |
yyyy | 以四位数字格式显示年份。 |
Calendar类
Math类
两个静态常量 E PI
long abs(double a) 返回a的绝对值
double max(double a, double b) 返回ab最大值
double min(double a, double b) 返回ab最小值
double pow(double a,double b) 返回a的b次幂
double sqrt(double a) 返回a的平方根
double cbrt(double a )返回a的立方根
double log(double a) 返回a的对数 以E为底
double sin(double a) 返回正弦值
double asin(double a) 返回反正弦值
BigDecimal类
因为不论是float 还是double都是浮点数,而计算机是二进制的,浮点数会失去一定的精确度
注:根本原因是:十进制值通常没有完全相同的二进制表示形式;十进制数的二进制表示形式可能不精确。只能无限接近于那个值
1、参数类型为double的构造方法的结果有一定的不可预知性。有人可能认为在Java中写入newBigDecimal(0.1)所创建的BigDecimal正好等于 0.1(非标度值 1,其标度为 1),但是它实际上等于0.1000000000000000055511151231257827021181583404541015625。这是因为0.1无法准确地表示为 double(或者说对于该情况,不能表示为任何有限长度的二进制小数)。这样,传入到构造方法的值不会正好等于 0.1(虽然表面上等于该值)。
2、另一方面,String 构造方法是完全可预知的:写入 newBigDecimal(“0.1”) 将创建一个 BigDecimal,它正好等于预期的 0.1。因此,比较而言,通常建议优先使用String构造方法
[https://blog.csdn.net/qq_35868412/article/details/89029288]:
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.DecimalFormat;
public class Example {
public static void main(String[] args) {
System.out.println(2.0-1.1);
// 构造方法 常用BigDecimal(String)
BigDecimal a = new BigDecimal("2.0000");
BigDecimal b = new BigDecimal("1.1000");
System.out.println(a.subtract(b));
System.out.println(a.divide(b, 2, RoundingMode.HALF_UP));
BigDecimal c = new BigDecimal("3.1415926");
System.out.println(c.setScale(2,RoundingMode.HALF_UP));
}
}
/*舍入模式
ROUND_CEILING //向正无穷方向舍入
ROUND_DOWN //向零方向舍入
ROUND_FLOOR //向负无穷方向舍入
ROUND_HALF_DOWN //向(距离)最近的一边舍入,除非两边(的距离)是相等,如果是这样,向下舍入, 例如1.55 保留一位小数结果为1.5
ROUND_HALF_EVEN //向(距离)最近的一边舍入,除非两边(的距离)是相等,如果是这样,如果保留位数是奇数,使用ROUND_HALF_UP,如果是偶数,使用ROUND_HALF_DOWN
ROUND_HALF_UP //向(距离)最近的一边舍入,除非两边(的距离)是相等,如果是这样,向上舍入, 1.55保留一位小数结果为1.6,也就是我们常说的“四舍五入”
ROUND_UNNECESSARY //计算结果是精确的,不需要舍入模式
ROUND_UP //向远离0的方向舍入
Pattern和Match类
字符串匹配
System类
exit 退出当前程序 Sustem.exit()
gc 运行垃圾回收机制, System.gc()
currentTimeMillens返回当前距离1970-1-1的毫秒数
集合
1. 集合的理解和好处
- 可以动态保存任意多个对象,使用比较方便
- 提供一系列方便操作对象的方法,add、remove、set、get
- 使用集合添加删除新元素的示意代码 --简洁了
2.集合的框架体系
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-opFgAa6N-1671179039720)(E:\新建文件夹\java\image\集合\集合框架.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fNBRUlak-1671179039720)(E:\新建文件夹\java\image\集合\集合框架2.png)]
//1集合主要是两组(单列集合,双列集合) 区别如下代码, 双列需键和值
//2. Collection 接口有两个重要的子接口 List Set , 他们的实现子类都是单列集合
//3. Map 接口的实现子类 是双列集合,存放的 K-V
import java.util.ArrayList;
import java.util.HashMap;
public class Collction_ {
public static void main(String[] args) {
ArrayList arrayList = new ArrayList();
arrayList.add("小明");
arrayList.add("小兰");
HashMap hashMap = new HashMap();
hashMap.put("NO1", "北京");
hashMap.put("NO2", "上海");
}
}
3.Collection接口和常用方法
collection介绍
-
collection实现子类可以存放多个元素,每个元素可是Object
-
collection的实现类可以存放重复的元素,有些不可以
-
collection的实现类,有些是有序的(list) 有些是无序的(set)
-
没有直接的实现子类,是通过它的子接口Set和List来实现的
常用方法
以实现子类ArrayList演示
import java.util.ArrayList;
import java.util.List;
public class Collction_ {
@SuppressWarnings({ "all"})
public static void main(String[] args) {
List arrayList = new ArrayList();
// 添加元素add()
arrayList.add("小梦");
arrayList.add(22); //实际上是添加的Integer对象
arrayList.add(true);
System.out.println(arrayList);
// 删除元素remove() 可以加位置,可以加具体值
arrayList.remove(0);
// arrayList.remove(true);
System.out.println(arrayList);
// 是否含有contains()
System.out.println(arrayList.contains(22));
// 获取元素个数size()
System.out.println(arrayList.size());
// 判断是否为空 isEmpty()
System.out.println(arrayList.isEmpty());
// 清空clear()
arrayList.clear();
System.out.println(arrayList);
// 添加多个元素addAll()
List list2 = new ArrayList();
list2.add("三国演义");
list2.add("红楼梦");
arrayList.addAll(list2);
System.out.println(arrayList);
// 判断多个元素是否存在containsAll()
System.out.println(arrayList.containsAll(list2));
// 删除多个元素removeAll()
arrayList.removeAll(list2);
System.out.println(arrayList);
}
}
/*运行结果
[小梦, 22, true]
[22, true]
true
2
false
[]
[三国演义, 红楼梦]
true
[]
Collection接口遍历元素方式
- Iterator对象称为迭代器,主要用于遍历Collection集合中的元素
- 所有实现了Collection接口的集合类都有一个iterator()方法
- Iterator仅用于遍历集合,Iterator本身不存放对象
- 结构如图(在调用next()方法之前必须调用hasNext()
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jC8n1aU4-1671179039720)(E:\新建文件夹\java\image\集合\迭代器执行原理.png)]
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
public class Iterator_ {
@SuppressWarnings({"all"})
public static void main(String[] args) {
Collection arrayList = new ArrayList();
arrayList.add(new Book("三国演义", "罗贯中", 12) );
arrayList.add(new Book("红楼梦", "曹雪芹", 19) );
arrayList.add(new Book("水浒传", "施耐庵", 30) );
// System.out.println(arrayList);
// 增强for循环
// 1.
// for (Iterator iterator = arrayList.iterator(); iterator.hasNext();) {
// Object object = iterator.next();
// System.out.println(object);
//
// }
// 2.
// for(Object book: arrayList) {
// System.out.println(book);
// }
Iterator iterator = arrayList.iterator();
while (iterator.hasNext()) {
Object object = (Object) iterator.next();
System.out.println(object);
}
// 第二次使用迭代器需重置
iterator = arrayList.iterator();
while (iterator.hasNext()) {
Object object = (Object) iterator.next();
System.out.println(object);
}
}
}
class Book{
private String name;
private String author;
private int price;
public Book(String name,String author,int price) {
this.name = name;
this.price = price;
this.author = author;
// TODO Auto-generated constructor stub
}
@Override
public String toString() {
// TODO Auto-generated method stub
return "书名:" + name + " 作者:" + author + " 价格:" +price;
}
}
/*运行结果
书名:三国演义 作者:罗贯中 价格:12
书名:红楼梦 作者:曹雪芹 价格:19
书名:水浒传 作者:施耐庵 价格:30
书名:三国演义 作者:罗贯中 价格:12
书名:红楼梦 作者:曹雪芹 价格:19
书名:水浒传 作者:施耐庵 价格:30
4.List接口(继承collection
(1)List接口介绍
List接口是collection接口的子接口
- 元素有序,即添加按顺序和取出顺序一致,且可重复
- 每个元素都有其对应的顺序索引,从0开始
- list实现类(vector,linklist)遍历方法一样
List常用接口和方法
import java.util.ArrayList;
import java.util.List;
public class List_ {
@SuppressWarnings({"all"})
public static void main(String[] args) {
List list = new ArrayList();
list.add("喜羊羊");
list.add("懒羊羊");
list.add("沸羊羊");
list.add("暖羊羊");
// void add(int index, Object ele):在 index 位置插入 ele 元素
list.add(1, "美羊羊");
System.out.println(list);
// boolean addAll(int index, Collection eles):
// 从 index 位置开始将 eles 中的所有元素添加进来
List list2 = new ArrayList();
list2.add("jack");
list2.add("tom");
list.addAll(1, list2);
System.out.println(list);
// Object get(int index):获取指定 index 位置的元素
System.out.println(list.get(3));
// int indexOf(Object obj):返回 obj 在集合中首次出现的位置
System.out.println(list.indexOf("jack"));
// int lastIndexOf(Object obj):返回 obj 在当前集合中末次出现的位置
list.add(4,"jack");
System.out.println(list.lastIndexOf("jack"));
// Object remove(int index):移除指定 index 位置的元素,并返回此元素
System.out.println(list.remove(1));
System.out.println(list);
// Object set(int index, Object ele):
// 设置指定 index 位置的元素为 ele , 相当于是替换
list.set(0, "喜羊羊1");
System.out.println(list);
// List subList(int fromIndex, int toIndex):
// 返回从 fromIndex 到 toIndex 位置的子集合[fromIndex, toIndex)
// 注意返回的子集合 fromIndex <= subList < toIndex
System.out.println(list.subList(2, 5));
}
}
/*
[喜羊羊, 美羊羊, 懒羊羊, 沸羊羊, 暖羊羊]
[喜羊羊, jack, tom, 美羊羊, 懒羊羊, 沸羊羊, 暖羊羊]
美羊羊
1
4
jack
[喜羊羊, tom, 美羊羊, jack, 懒羊羊, 沸羊羊, 暖羊羊]
[喜羊羊1, tom, 美羊羊, jack, 懒羊羊, 沸羊羊, 暖羊羊]
[美羊羊, jack, 懒羊羊]
简单排序
package List接口;
import java.util.*;
@SuppressWarnings({"all"})
public class ListSort {
public static void main(String[] args) {
List list = new ArrayList();
list.add(new Book("三国演义", "罗贯中", 12) );
list.add(new Book("红楼梦", "曹雪芹", 69) );
list.add(new Book("水浒传", "施耐庵", 30) );
System.out.println("===排序前===");
for(Object obj:list) {
System.out.println(obj);
}
Book book = new Book();
Book.bubuSort(list);
System.out.println("===排序后===");
for(Object obj:list) {
System.out.println(obj);
}
}
}
class Book {
private String name;
int price;
private String author;
public Book() {
// TODO Auto-generated constructor stub
}
public Book(String name, String author, int price) {
// TODO Auto-generated constructor stub
this.name = name;
this.price = price;
this.author = author;
}
@Override
public String toString() {
// TODO Auto-generated method stub
return "name:"+name+"\tprice:"+price+"\tauthor:"+author;
}
public static void bubuSort(List list) {
int listsize = list.size();
for(int i = 0; i < listsize-1 ; i++) {
for(int j = 0; j < listsize-1-i; j++) {
Book book1 = (Book)list.get(j);
Book book2 = (Book)list.get(j+1);
if(book1.price > book2.price) {
list.set(j, book2);
list.set(j+1, book1);
}
}
}
}
}
/*
===排序前===
name:三国演义 price:12 author:罗贯中
name:红楼梦 price:69 author:曹雪芹
name:水浒传 price:30 author:施耐庵
===排序后===
name:三国演义 price:12 author:罗贯中
name:水浒传 price:30 author:施耐庵
name:红楼梦 price:69 author:曹雪芹
(2)ArrayList
ArrayList底层结构和源码分析
注意事项
- 可以存放所有元素包括空元素
- 是由数组来实现数据存储的
- 基本等同于Vector,除了ArrayList是线程不安全(但执行效率高)多线程 不建议使用ArayyList
底层操作机制
- ArrayList中维护了一个Object类型的数组elementData(transient Object[] elementData transient表示瞬间,表示该属性不会被序列化
- 当创建对象时,如果使用无参构造器,则初始elementData容量为0,第一次添加,扩容为10,如需再次扩容,则扩容为1.5倍
- 如果用指定大小构造器,初始化容量为指定大小,如需扩容,按当前1.5倍
当执行add()方法时,首先会调用ensureCapacityInternal()这个方法,来保证容量足够,之后再将新添加的元素e放到新扩充容量的位置。我们再来细看ensureCapacityInternal()这个方法
ensureCapacityInternal()上面传入的参数是size+1,size是当前数组的元素个数大小。方法进来以后,先判断当前数组是否为空数组,DEFAULTCAPACITY_EMPTY_ELEMENTDATA是ArrayList前面定义的空数组,如果是的话,则取定义的最小容量和默认大小的最大值,DEFAULT_CAPACITY大小前面定义为10。之后在进行ensureExplicitCapacity(minCapacity)的方法。
如果新扩充容量大于目前元素的大小时,则终于开始进行扩容grow()方法。
在grow()方法中,首先对旧容量oldCapacity进行1.5倍的扩容,之后如果newCapacity仍然小于最小要求容量minCapacity,就把minCapacity的值给到newCapacity。如果newCapacity的值大于最大要求容量,则会走hugeCapacity()方法,进行一个最终扩容后的大小确认,最后调用Arrays.copyof()方法,在原数组的基础上进行复制,容量变为newCapacity的大小。
扩容使用的是Arrays.copyOf(),扩容后的地址会改变,输入长度为实际有效数值的长度,不包含null。容量和size是有区别的
(3)Vector底层结构
介绍
- vector底层也是一个对象数组 ,protected Object[] elementData
- vector 是线程同步的,即线程安全,Vector每个方法都带有synchronized
- 开发中需要线程同步安全时考虑使用vector
- 扩容机制: 无参默认十,满后两倍扩容 如果指定大小每次按两倍扩容
(4)LinkedList底层结构
linkedlist介绍
- linkedlist底层维护了一个双向链表(!!!
- 维护了两个属性first和last分别指向首节点和尾节点
- 每个节点(Node对象),里面又维护了prev,next,item三个属性,其中通过prev指向前一个,next指向后一个节点,实现双向链表
- LinkedList的删除和添加不是以来数组完成的,所以效率高
底层源码:
- 增加
第一次添加节点如图
第二次添加节点如图
-
删除
(1) remove() 默认删除头节点
public E remove() { return removeFirst(); } public E removeFirst() { final Node<E> f = first; if (f == null) throw new NoSuchElementException(); return unlinkFirst(f); } private E unlinkFirst(Node<E> f) { // assert f == first && f != null; final E element = f.item; final Node<E> next = f.next; f.item = null; f.next = null; // help GC first = next; if (next == null) last = null; else next.prev = null; size--; modCount++; return element; }
(2) remove(Object o ) 删除指定元素
(3) remove(int index ) 删除指定位置的元素
-
set(int index ,Object o ) 改变指定位置的元素
-
get(int index) 获得指定位置的元素
5.Set接口(继承collection
(1)Set接口介绍
- set接口实现类的对象(set接口对象)不能存放重复的元素,可以放null
- set接口对象存放数据是无序的,添加和取出的顺序不一致
- 取出的顺序虽不是添加的顺序,但是他是固定的
- 不可以通过索引取出元素
- 继承了collection接口,遍历方法与collection遍历相同
- 常用方法 add() /返回boolean值,是否添加成功 remove(Object o)
(2)HashSet
hashset介绍
-
HashSet实现了set接口
-
HashSet实际上是HashMap
-
可以存放null值但只能有一个
-
hashset不保证元素是有序的,取决于hash后,在确定索引的结果
public HashSet(){ map = new HashMap<>(); }
-
不能有重复元素/对象
package HashMap_;
import java.util.HashSet;
import java.util.Set;
public class HashMap_ {
public static void main(String[] args) {
Set set = new HashSet();
System.out.println(set.add("小红"));//true
System.out.println(set.add("John"));//true
System.out.println(set.add("Jack"));//true
System.out.println(set.add("小红"));//false重复元素
System.out.println(set);
//[小红, John, Jack]
set = new HashSet();
System.out.println(set.add(new Dog("big yellow")));//true
System.out.println(set.add(new Dog("big yellow")));//true
System.out.println(set);
//[name:big yellow, name:big yellow]分析源码阶段解释原因
System.out.println(set.add(new String("hong")));//true
System.out.println(set.add(new String("hong")));//false
System.out.println(set);
//[hong, name:big yellow, name:big yellow] 为什么都是new一个对象结果却不同?
}
}
class Dog{
String name;
public Dog(String name) {
// TODO Auto-generated constructor stub
this.name = name;
}
@Override
public String toString() {
// TODO Auto-generated method stub
return "name:"+ name;
}
}
hashset添加元素底层
-
底层是HashMap,HashMap底层是(数组+链表+红黑树)
-
添加元素时,先得到hash值,转成-》索引值 hash()+equals()
-
找到存储数据表table,看这个索引位置是否已经存放了元素
-
如果没有,直接加入
-
如果没有,调用equals比较,如果相同,就放弃添加,如果不同,则添加到最后(字符串的equals方法被改写,比较内容) equals()方法由程序员决定,不能够简单的理解字符串的内容
-
数组(table)加链表示意图[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vGZCUpeT-1671179039722)(E:\新建文件夹\java\image\集合\hashset\数组加链表示意图.png)]
-
在Java8中,如果一条链表元素个数超过TREEIFY_THRESHOLD(默认8),并且table大小》=MIN_TREEIFY_CAPACITY(默认64),就会进行树化(红黑树
-
底层源码:
public boolean add(E e) { return map.put(e, PRESENT)==null; } public V put(K key, V value) { return putVal(hash(key), key, value, false, true); } //hash值并不是hashcode,做过处理,为了避免碰撞 static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); } //核心代码 HashMap类 final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { Node<K,V>[] tab; Node<K,V> p; int n, i;//定义辅助变量 //table是hashmap的一个属性,类型为Node[] if ((tab = table) == null || (n = tab.length) == 0) n = (tab = resize()).length; //跳入resize()函数,此后table变为16个大小,第一次扩容 //(1) 根据key,得到hash,计算key应该存放在table的哪个位置,然后赋值给p //(2) 判断p是否为空 //(2.1) 如果p为空,则表示没有元素,创建应该Node(key, value) 最重要的两个值 if ((p = tab[i = (n - 1) & hash]) == null) //无论是否符合都会执行p = tab[i = (n - 1)] 让p指向计算出的索引的第一个元素 tab[i] = newNode(hash, key, value, null); //传hash为了判断该位置是否有元素 else { Node<K,V> e; K k; //如果当前索引对应链表的第一个元素与准备添加的key的hash相同, //并且(p指向的node节点的key与准备加入的key是同一个对象)或者(p指向的node节点的key的equals()和准备加入到key比较后相同) //则不能添加 if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) e = p; //判断p是否为一棵红黑树,如果是,调用putTreeVal进行添加 else if (p instanceof TreeNode) e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); else { //如果table索引位置已经是一个链表则使用for循环进行比较 //(1)依次比较链表中每一个元素,不相同,则加入到链表最后一个(第一个if语句 //把元素添加到链表后,立即判断链表是否已经达到8个节点,如果是调用treeifyBin(tab, hash);对该链表进行树化(转成红黑树 //转成红黑树时要先进行判断,判断条件: //if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY) // 如果条件成立,则先对table进行扩容,不成立在进行树化 //(2)比较过程中,遇到相同的直接退出(第二个if语句 for (int binCount = 0; ; ++binCount) { if ((e = p.next) == null) {//把p的下一个元素赋给e p.next = newNode(hash, key, value, null); if (binCount >= TREEIFY_THRESHOLD(8) - 1) // -1 for 1st treeifyBin(tab, hash); break; } if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break; p = e; //p往下移 } } if (e != null) { // existing mapping for key V oldValue = e.value; if (!onlyIfAbsent || oldValue == null) e.value = value; afterNodeAccess(e); return oldValue; } } ++modCount; if (++size > threshold) //判断放入元素之后大小是否超过临界值 resize(); afterNodeInsertion(evict); //空方法 为了让hashmap子类实现 return null; //代表成功 (传回put()方法,再传回add()方法,在此方法中判断是否为null,返回bool值 } final Node<K,V>[] resize() { Node<K,V>[] oldTab = table; int oldCap = (oldTab == null) ? 0 : oldTab.length; int oldThr = threshold; int newCap, newThr = 0; if (oldCap > 0) { if (oldCap >= MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return oldTab; } else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY) newThr = oldThr << 1; // double threshold } else if (oldThr > 0) // initial capacity was placed in threshold newCap = oldThr; else { // zero initial threshold signifies using defaults newCap = DEFAULT_INITIAL_CAPACITY; newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); //newThr为临界值,防止数据大量添加 DEFAULT_LOAD_FACTOR=0.75 DEFAULT_INITIAL_CAPACITY = 16 } if (newThr == 0) { float ft = (float)newCap * loadFactor; newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? (int)ft : Integer.MAX_VALUE); } threshold = newThr; @SuppressWarnings({"rawtypes","unchecked"}) Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; table = newTab; if (oldTab != null) { for (int j = 0; j < oldCap; ++j) { Node<K,V> e; if ((e = oldTab[j]) != null) { oldTab[j] = null; if (e.next == null) newTab[e.hash & (newCap - 1)] = e; else if (e instanceof TreeNode) ((TreeNode<K,V>)e).split(this, newTab, j, oldCap); else { // preserve order Node<K,V> loHead = null, loTail = null; Node<K,V> hiHead = null, hiTail = null; Node<K,V> next; do { next = e.next; if ((e.hash & oldCap) == 0) { if (loTail == null) loHead = e; else loTail.next = e; loTail = e; } else { if (hiTail == null) hiHead = e; else hiTail.next = e; hiTail = e; } } while ((e = next) != null); if (loTail != null) { loTail.next = null; newTab[j] = loHead; } if (hiTail != null) { hiTail.next = null; newTab[j + oldCap] = hiHead; } } } } } return newTab; }
扩容机制
- HashSet底层是HashMap,第一次添加时,table数组扩容到16,临界值(threshold)是16*加载因子(loadFactor)是0.75 = 12
- 如果table数组使用到了临界值12,就会扩容到16 * 2=32,新的临界值变为32 * 0.75=24,以此类推
- 在java8中,一条链表的元素个数到达TREEIFY_THRESHOLD(默认8)并且table大小>=MIN_TREEIFY_CAPACITY(默认64),就会进行树化,否则仍然采用数组扩容
- size()指的是 加入的元素总数(包括链表上的数),而不单单是table上的元素
例题
得出hashcode-》推出hash值-》euqals()判断-》放入
public class Hello {
public static void main(String[] args) {
HashSet hashSet = new HashSet();
hashSet.add(new Employee("MILAN",18));
hashSet.add(new Employee("MILAN",18));
hashSet.add(new Employee("MILAN",18));
System.out.println(hashSet);
}
}
class Employee{
private String name;
private int age;
public Employee(String name, int age) {
this.name = name;
this.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;
}
@Override
public String toString() {
return "Employee{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Employee employee = (Employee) o;
return age == employee.age && Objects.equals(name, employee.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
//只要name和age一样那么返回的hashCode就一样
//原理,用name和age的hashCode返回对象的hashCode
}
hash的实现:根据每个元素的hashCode 通过算法返回一个hashCode
public static int hash(Object... values) {
return Arrays.hashCode(values);
}
public static int hashCode(Object[] a) {
if (a == null)
return 0;
int result = 1;
for (Object element : a)
result = 31 * result + (element == null ? 0 : element.hashCode());
return result;
}
(3)LinkedHashSet
LinkedHashSet介绍
- LinkedHashSet是HashSet的子类
- 底层是应该LinkedHashMap,底层维护了一个数组加双向链表(有head和tail
- 根据元素的hashCode值来决定元素的存储位置,用链表维护元素的次序。[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9mldgDIu-1671179039723)(E:\新建文件夹\java\image\集合\linkedhashset\数组+双向链表.png)]
- 每一个节点都有pre 和next属性,形成双向链表
- 添加一个元素时,先求hash值,再求索引,确定该元素在table的位置,然后讲添加的元素加入到双线链表(如果已经存在不添加,原则和hashset相同) tail.next = new Element newElement.pre = tail tail = newElement
- 这样的化我们遍历LinkedHashSet也能确保插入顺序和遍历顺序一致
底层源码
- LinkedHashSet加入元素和取出元素的顺序一致
- LinkedHashSet底层维护一个LinkedHashMap(是HashMap的子类
- LinkedHashSet底层结构(数组table+双线链表
- 添加第一次时,直接数组table扩容到16,存放的结点是LinkedHashMap$Entry类型
- 数组是HashMap$Node[]类型
- add()方法仍然是HashMap中的putVal()方法
//linkedhashmap类
//Entry继承HashMap.Node(Node是内部静态类,可以通过类名直接访问),故table是Node类,而元素是Entry类
static class Entry<K,V> extends HashMap.Node<K,V> {
Entry<K,V> before, after;
Entry(int hash, K key, V value, Node<K,V> next) {
super(hash, key, value, next);
}
}
(4)TreeSet类
-
使用无参构造器,元素仍然是无序的
-
使用TreeSet提供的一个构造器,可以传入一个比较器(匿名内部类)并指定排序规则
-
构造器把传入的比较器对象,赋给了 TreeSet 的底层的 TreeMap 的属性 this.comparator ,
public TreeMap(Comparator<? super K> comparator) {
this.comparator = comparator;
}
//hashmap类 private V put(K key, V value, boolean replaceOld) { Entry<K,V> t = root;//赋给t根节点 if (t == null) { addEntryToEmptyMap(key, value); /*private void addEntryToEmptyMap(K key, V value) { compare(key, key); // type (and possibly null) check判断是否为空 root = new Entry<>(key, value, null); size = 1; modCount++; } */ return null; } int cmp; Entry<K,V> parent; // split comparator and comparable paths Comparator<? super K> cpr = comparator; //将匿名类赋值给cpr if (cpr != null) { //有参构造器,cpr一定不为空,if后有else此时省略 do { parent = t;//不断更新父节点 cmp = cpr.compare(key, t.key); //动态绑定到匿名内部类cpr if (cmp < 0) t = t.left; else if (cmp > 0) t = t.right; else { //如果相等,则返回0,这个key就没有加入 V oldValue = t.value; if (replaceOld || oldValue == null) { t.value = value; } return oldValue; } } while (t != null); addEntry(key, value, parent); return null; }
//例 public class TreeSet_ { public static void main(String[] args) { TreeSet treeSet = new TreeSet(new Comparator() { @Override public int compare(Object o1, Object o2) { return ((String)o2).compareTo((String) o1); } }); treeSet.add("jack"); treeSet.add("tom"); treeSet.add("sp"); treeSet.add("a"); System.out.println(treeSet); } }
6.Map接口和常用方法
(1)Map接口介绍
-
Map和Collection并列存在,用于保存具有映射关系的数据:Key–Value
-
Map中的key和value是任何引用类型的数据,会封装到HashMap$Node对象中
-
Map中的key不允许重复,当有相同的Key时,等价于替换,原因和HashSet一样,前面分析过源码
-
Map中的value可以重复
-
Map中key和value都可以为空,但key只能有一个空,value能有多个空
-
package Map接口; import java.util.HashMap; import java.util.Map; @SuppressWarnings({"all"}) public class Map_ { public static void main(String[] args) { Map map = new HashMap(); map.put("no1", "xyy"); map.put("no2", "myy"); map.put("no1", "nyy");//等价替换 System.out.println("map:"+map); map.put("no3", "xyy"); System.out.println("map:"+map); map.put(null, null); System.out.println("map:"+map); map.put(null, "fyy"); System.out.println("map:"+map); map.put("no4", null); map.put("no5", null); System.out.println("map:"+map); } } /* map:{no2=myy, no1=nyy} map:{no2=myy, no1=nyy, no3=xyy} map:{no2=myy, null=null, no1=nyy, no3=xyy} map:{no2=myy, null=fyy, no1=nyy, no3=xyy} map:{no2=myy, null=fyy, no1=nyy, no4=null, no3=xyy, no5=null}
-
一般String类作为Map的key,实际上不仅String可以(只要是Object子类都可以
-
key和value唯一对应
底层源码
- Map存放数据key–value示意图:
-
k-v 最后是HashMap$Node node = newNode(hash,key,value,null)
-
k-v 为了方便程序员遍历, 还会创建EntrySet集合,该集合存放的元素类型是Entry,而一个Entry对象就有 k-v EntrySet<Entry<K,V>>, 即 :tansient Set<Map.Entry<K,V>> entrySet
-
EntrySet中,定义的类型是Map.Entry 但是实际上存放的还是HahsMap$Node (HashMap Node实现了Map.Entry接口,
-
Map.Entry提供了 K getKey() V getValue() 方法
-
import java.util.HashMap; import java.util.Map; import java.util.Set; public class MAPSource { public static void main(String[] args) { Map map = new HashMap(); map.put("no1", "xyy"); map.put("no2", "myy"); Set set = map.entrySet(); System.out.println(set.getClass());//HashMap$EntrySet for (Object obj : set) { // System.out.println(obj.getClass()); //class java.util.HashMap$Node // 为了从HashMap$Node取出k-v // 先做一个向下转型 Map.Entry entry = (Map.Entry) obj; System.out.println(entry.getKey()+"--"+entry.getValue()); } } } /* class java.util.HashMap$EntrySet no2--myy no1--xyy
-
-
table表(数组+链表+红黑树) 为了方便管理在底层做处理,将每一个Node封装成一个Entry,再把Entry放到集合EntrySet中
-
常用方法
import java.util.HashMap;
import java.util.Map;
public class MAPSource {
public static void main(String[] args) {
Map map = new HashMap();
map.put("no1", "xyy");
map.put("no2", "myy");
map.put("no3", "fyy");
map.put("no4", "nyy");
map.put("no5", "htl");
System.out.println(map);
// remove() 根据键删除映射关系
map.remove("no1");
System.out.println(map);
// get() 根据键获取值
Object val = map.get("no2");
System.out.println(val);
// size() 获取当前有多少对
System.out.println(map.size());
// isEmpty() 判断为空
System.out.println(map.isEmpty());
//修改
map.put("no5","lyy");
// clear() 清空
map.clear();
System.out.println(map);
// containsKey() 查找键是否存在
System.out.println(map.containsKey("no1"));
}
}
/*
{no2=myy, no1=xyy, no4=nyy, no3=fyy, no5=htl}
{no2=myy, no4=nyy, no3=fyy, no5=htl}
myy
4
false
{}
false
遍历方法
import java.util.*;
@SuppressWarnings({"all"})
public class Example {
public static void main(String[] args) {
Map map = new HashMap();
map.put("no1", "xyy");
map.put("no2", "myy");
map.put("no3", "fyy");
map.put("no4", "nyy");
map.put("no5", "htl");
// 第一组先取出所有的key 通过key,去除相应的value
Set keyset = map.keySet();
// 增强for
for (Object key :keyset) {
System.out.println(key+"--"+map.get(key));
}
// 迭代器
Iterator iterator = keyset.iterator();
while (iterator.hasNext()){
Object key = iterator.next();
System.out.println(key+"--"+map.get(key));
}
// 第二组,把所有values取出来
Collection values = map.values();
// 可以使用Collection使用的遍历方法
System.out.println("取出所有value");
for (Object o :values) {
System.out.println(o);
}
System.out.println("取出所有value");
for (Iterator iterator1 = values.iterator(); iterator1.hasNext();
){
Object obj=iterator1.next();
System.out.println(obj);
}
System.out.println("取出所有value");
Iterator iterator1 = values.iterator();
while(iterator1.hasNext()){
Object obj = iterator1.next();
System.out.println(obj);
}
// 第三组,通过EntrySet
Set entrySet = map.entrySet();
for (Object entry: entrySet) {
// 将entry对象转成Map.Entry
Map.Entry entry1 = (Map.Entry)entry;
System.out.println(entry1.getKey()+"--"+entry1.getValue());
}
// 迭代器
Iterator iterator2 = entrySet.iterator();
while (iterator2.hasNext()){
Object next = iterator2.next(); //此时next为HashMap$Node类
//实现-》Map.Entry(getKey,getValue)
Map.Entry entry = (Map.Entry) next;
System.out.println(entry.getKey()+"--"+entry.getValue());
}
}
}
练习
package Map接口.练习;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
@SuppressWarnings({"all"})
public class Example {
public static void main(String[] args) {
Map map = new HashMap();
Employee e1 = new Employee(250,"小明",19000);
Employee e2 = new Employee(251,"小红",17000);
Employee e3 = new Employee(252,"小蓝",20000);
map.put("250",e1);
map.put("251",e2);
map.put("252",e3);
Set set = map.keySet();
System.out.println("第一种");
for (Object key :set) {
Employee emp = (Employee)map.get(key);
if(emp.getSalary()>=19000){
System.out.println(key+"--"+map.get(key));
}
}
System.out.println("第二种");
Set entrySet = map.entrySet();
Iterator iterator = entrySet.iterator();
while (iterator.hasNext()){
Map.Entry entry = (Map.Entry)iterator.next();
Employee emp = (Employee) entry;
if (emp.getSalary()>=19000){
System.out.println(entry.getKey()+"--"+entry.getValue());
}
}
}
}
class Employee{
private int id;
private String name;
private int salary;
public Employee(int id, String name, int salary) {
this.id = id;
this.name = name;
this.salary = salary;
}
public int getId() {
return id;
}
public String getName() {
return name;
}
public int getSalary() {
return salary;
}
@Override
public String toString() {
return "id:"+id+" name:"+name+"salary:"+salary;
}
}
/*
第一种
250--id:250 name:小明salary:19000
252--id:252 name:小蓝salary:20000
第二种
250--id:250 name:小明salary:19000
252--id:252 name:小蓝salary:20000
(2)HashMap类
介绍
- 是Map接口使用频率最高的实现类
- 以key的hash和equals判断是否可以添加
- 没有实现同步,所以是线程不安全的
- 与hashset一样,不保证顺序,底层是hash表的方式存储(数组+链表+红黑树)
底层机制
可能一条是链表,而另一条是红黑树
扩容机制和hashSet完全一样
(3)HashTable类
介绍
-
存放的元素是键值对:即k-v
-
hashtable的键和值都不能为null,否则会抛出NullPointerException异常
-
使用方法基本和HashMap一样
-
HashTable线程安全(synchronized),HashMap是线程不安全的
底层
-
底层有数组HashTable$Entry[],初始化大小为11
-
threshold(临界值) 8= 11*0.75
-
按照自己的扩容(有自己的扩容机制
-
满足 if (count >= threshold) ,则执行rehash()方法
-
rehash()方法中,进行扩容的原理: int newCapacity = (oldCapacity << 1) + 1; 最终扩容到语句 Entry<?,?>[] newMap = new Entry<?,?>[newCapacity];
//添加元素 private void addEntry(int hash, K key, V value, int index) { Entry<?,?> tab[] = table; if (count >= threshold) { // Rehash the table if the threshold is exceeded rehash(); tab = table; hash = key.hashCode(); index = (hash & 0x7FFFFFFF) % tab.length; } // Creates the new entry. @SuppressWarnings("unchecked") Entry<K,V> e = (Entry<K,V>) tab[index]; tab[index] = new Entry<>(hash, key, value, e); count++; modCount++; } //扩容进入rehash() protected void rehash() { int oldCapacity = table.length; Entry<?,?>[] oldMap = table; // overflow-conscious code int newCapacity = (oldCapacity << 1) + 1; if (newCapacity - MAX_ARRAY_SIZE > 0) { if (oldCapacity == MAX_ARRAY_SIZE) // Keep running with MAX_ARRAY_SIZE buckets return; newCapacity = MAX_ARRAY_SIZE; } Entry<?,?>[] newMap = new Entry<?,?>[newCapacity]; modCount++; threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1); table = newMap; for (int i = oldCapacity ; i-- > 0 ;) { for (Entry<K,V> old = (Entry<K,V>)oldMap[i] ; old != null ; ) { Entry<K,V> e = old; old = old.next; int index = (e.hash & 0x7FFFFFFF) % newCapacity; e.next = (Entry<K,V>)newMap[index]; newMap[index] = e; } } }
(4)HashMap和HashTable对比
(4)Properties类
介绍
- Properties 类继承Hashtable类并实现Map接口,也是键值对形存储数据
- 特点和Hashtable类似
- Properities可用于从xxx.properties文件中,加载到Properties对象,并进行读取和修改
- 工作后xxx.properties文件通常作为配置文件,
(5)TreeMap
TreeSet底层就是TreeMap
7.如何选择集合实现类
- 先判断存储类型(一组对象或者一组键值对)
- 一组对象[单列]:Collection接口
允许重复:List:
增删多:LinkedList(底层维护一个双向链表
改查多:ArrayList(底层维护Object类型的数组
不允许重复:
无序:HashSet(底层是HashMap,维护了一个hash表(数组+链表+红黑树)
排序:TreeSet
插入和取出顺序一致:LinkedHashSet(维护数组+双向链表
-
一组键值对[双列]
键无序:HashMap(底层是Hash表,数组+链表+红黑树(jdk7,是数组+链表
键排序:TreeMap
键插入和取出顺序一致:LinkeHashMap
读取文件:properties
8.Collections工具类
- Collectionsshi一个操作Set,List和Map等集合的工具类
- 提供了一系列静态的方法对集合元素进行排序,查询和修改
- 第一组
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
@SuppressWarnings({"all"})
public class Methods {
public static void main(String[] args) {
ArrayList list = new ArrayList();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
list.add(5);
list.add(6);
System.out.println(list);
// reverse(list) 反转
Collections.reverse(list);
System.out.println(list);
// shuffle(list) 随机排序
Collections.shuffle(list);
System.out.println(list);
// sort(list) 排序
Collections.sort(list);
System.out.println(list);
// sort(list, Comparator) 根据Comparator对list进行排序
Collections.sort(list, new Comparator(){
@Override
public int compare(Object o1, Object o2) {
return (int)o2 - (int)o1;
}
});
System.out.println(list);
// swap(list, x, y)对x和y位置进行交换
Collections.swap(list,1,2);
System.out.println(list);
}
}
/*
[1, 2, 3, 4, 5, 6]
[6, 5, 4, 3, 2, 1]
[1, 6, 3, 4, 5, 2]
[1, 2, 3, 4, 5, 6]
[6, 5, 4, 3, 2, 1]
[6, 4, 5, 3, 2, 1]
- 第二组 查询和修改
package Collections工具类;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
@SuppressWarnings({"all"})
public class Methods2 {
public static void main(String[] args) {
ArrayList list = new ArrayList();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
list.add(5);
list.add(6);
// Object max(list) 返回自然排序下最大值
System.out.println(Collections.max(list));
// Object max(list, Comparator) 返回指定排序下最大值
System.out.println(Collections.max(list, new Comparator() {
@Override
public int compare(Object o1, Object o2) {
return (int)o2 - (int)o1;
}
}));
// Object min(list) 返回自然排序下最小值
System.out.println(Collections.min(list));
// Object max(list, Comparator) 返回指定排序下最小值
System.out.println(Collections.min(list, new Comparator() {
@Override
public int compare(Object o1, Object o2) {
return (int)o2 - (int)o1;
}
}));
list.add(2);
// int frequency(Collection, Object)返回指定元素出现的次数
System.out.println(Collections.frequency(list,2));
List list1 = new ArrayList();
list1.add(8);
list1.add(9);
// copy(dest, src) 将src中元素复制到dest
// 为了要完成一个完整的拷贝,要保证dest的大小大于src
Collections.copy(list,list1);
System.out.println(list);
// boolean replaceAll(list, Object oldVal, Object newVal) 使用新值替换list对象的旧值
Collections.replaceAll(list,2,10);
System.out.println(list);
}
}
/*
6
1
1
6
2
[8, 9, 3, 4, 5, 6, 2]
[8, 9, 3, 4, 5, 6, 10]
9.扩容机制总结
泛型
1.泛型的好处
- 编译时,检查元素的类型,提高了安全性
- 减少了类型转换的次数,提高了效率
- 不再提示编译警告
public class generic1 {
public static void main(String[] args) {
ArrayList<Dog> arrayList = new ArrayList<Dog>();
arrayList.add(new Dog("大黄",11));
arrayList.add(new Dog("老黑",7));
arrayList.add(new Dog("Tony",2));
// arrayList.add(new Cat("马内",2)); 报错,不能放Cat类型
for (Dog dog : arrayList) {
System.out.println(dog.getName()+" "+dog.getAge());
}
}
}
class Dog{
String name;
int age;
Dog(String name, int age){
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
class Cat{
String name;
int age;
Cat(String name, int age){
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
2.泛型介绍
-
泛型又称参数化类型,jdk5.0之后解决数据类型安全性的问题
-
在类声明或实例化时只要制定好需要的具体的类型即可
-
java泛型可以保证如果程序在编译时没有发出警告,运行时就不会产生ClassCastException异常,同时代码更加简洁,健壮
-
泛型的作用是:可以在类声明时通过一个标识表示类中某个属性的类型,或者是某个方法的返回值类型,或者是参数类型
class Person<E>{ E s; //E表示 s的数据类型,该类型在丁一Person对象时指定 public Person(E s){ //E表示参数类型 this.s = s; } public E f(){ // E表示返回类型 return s; } }
3.泛型使用
-
声明 interface 接口{} 和 class 类<K,V>{} (其中,KTV不代表值,而代表类型,任意字母都可以)
-
实例化 Lsit strList = new ArrayList() Iterator iterator = customor.iterator();
-
import java.util.*; @SuppressWarnings({"all"}) public class Example { public static void main(String[] args) { Student<String, Integer> hxm = new Student<>("HXM", 12); Student<String, Integer> yy = new Student<>("YY", 14); Student<String, Integer> yz = new Student<>("YZ", 11); // HashSet<Student> students = new HashSet<>(); // students.add(hxm); // students.add(yy); // students.add(yz); // // for (Student student :students) { // System.out.println(student); // } // // // System.out.println("第二组"); // Iterator<Student> iterator = students.iterator(); // while (iterator.hasNext()){ // System.out.println(iterator.next()); // } HashMap<Integer,Student> map = new HashMap<Integer,Student>(); map.put(1,hxm); map.put(2,yy); map.put(3,yz); for (Integer i :map.keySet()) { System.out.println(i+""+map.get(i)); } System.out.println("第二组"); Set<Map.Entry<Integer, Student>> entries = map.entrySet(); Iterator<Map.Entry<Integer, Student>> iterator = entries.iterator(); while (iterator.hasNext()){ Map.Entry<Integer, Student> next = iterator.next(); System.out.println(next.getKey()+""+next.getValue()); } } } class Student<E, V>{ private E name; private V age; public Student(E name, V age){ this.name = name; this.age = age; } public E getName() { return name; } public void setName(E name) { this.name = name; } public V getAge() { return age; } public void setAge(V age) { this.age = age; } @Override public String toString() { return "name:"+name+ " age:"+age; } }
4.使用细节
-
T,E 只能是引用类型,不能是基本类型
-
在指定泛型具体类型后,可以传入该类型或者其子类类型
-
使用形式 List< Integer > list1 = new ArrayList< Integer >();
实际开发中,经常简写
List< Integer > list3 = new ArrayList<>();
-
如果是 List list1 = new ArrayList(); E默认为Object
5.案例
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public class Example2 {
public static void main(String[] args) {
List<Employee> employees = new ArrayList<>();
employees.add(new Employee("小红",1200,new Date(2000,10,9)));
employees.add(new Employee("小蓝",1400,new Date(2001,1,8)));
employees.add(new Employee("小红",1700,new Date(1999,4,5)));
for (Employee employee :employees) {
System.out.println(employee);
}
System.out.println("============");
employees.sort( new Comparator<Employee>() {
@Override
public int compare(Employee o1, Employee o2) {
if(o1.getName().equals(o2.getName())){
return o1.getBirthday().compareTo(o2.getBirthday());
}
else {
return o1.getName().compareTo(o2.getName());
}
}
});
for (Employee employees1 :employees) {
System.out.println(employees1);
}
}
}
class Employee{
private String name;
private int salary;
private Date birthday;
public Employee(String name, int salary, Date birthday) {
this.name = name;
this.salary = salary;
this.birthday = birthday;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getSalary() {
return salary;
}
public void setSalary(int salary) {
this.salary = salary;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
@Override
public String toString() {
return "Employee{" +
"name='" + name + '\'' +
", salary=" + salary +
", birthday=" + birthday +
'}';
}
}
class Date implements Comparable<Date>{
private int month;
private int year;
private int day;
public int getMonth() {
return month;
}
public void setMonth(int month) {
this.month = month;
}
public int getYear() {
return year;
}
public void setYear(int year) {
this.year = year;
}
public int getDay() {
return day;
}
public void setDay(int day) {
this.day = day;
}
public Date(int year, int month, int day) {
this.month = month;
this.year = year;
this.day = day;
}
@Override
public String toString() {
return year+"-"+month+"-"+day;
}
@Override
public int compareTo(Date o) {
int yearMinus = year - o.year;
if (yearMinus!=0){
return yearMinus;
}
int monthMinus = month - o.month;
if (monthMinus!=0){
return monthMinus;
}
return day - o.day;
}
}
6.自定义泛型
自定义泛型类
数组不能初始化(数组在new时,不能确定T的类型,就无法在内存开空间
静态属性、静态方法不能使用泛型(静态在类加载时,对象还没有创建,不能确定T的类型,jvm无法完成初始化
创建对象时没有指定则默认为Object
class Student<E, V>{
private E name;
private V age;
public Student(E name, V age){
this.name = name;
this.age = age;
}
public E getName() {
return name;
}
public void setName(E name) {
this.name = name;
}
public V getAge() {
return age;
}
public void setAge(V age) {
this.age = age;
}
@Override
public String toString() {
return "name:"+name+ " age:"+age;
}
}
自定义泛型接口
interface 接口名<T,R…>{}
接口中,静态成员也不能使用泛型
泛型接口的类型,在继承接口和实现接口时确定
没有指定类型默认为Object
public class GnericI {
public static void main(String[] args) {
}
}
class IB implements Iusb<Integer,Double>{
@Override
public Double get(Integer integer) {
return null;
}
@Override
public void hi(Double aDouble) {
}
@Override
public void run(Double r1, Double r2, Integer u1, Integer u2) {
}
}
class IA implements Iusb2{
@Override
public Integer get(String s) {
return null;
}
@Override
public void hi(Integer integer) {
}
@Override
public void run(Integer r1, Integer r2, String u1, String u2) {
}
}
interface Iusb2 extends Iusb<String ,Integer>{
}
interface Iusb<U,R>{
//普通方法,可以使用接口泛型
R get(U u);
// U name; 报错
void hi(R r);
void run(R r1, R r2, U u1, U u2);
// 使用默认方法可以使用泛型
default R method(R r){
return r;
}
}
自定义泛型方法
泛型方法,可以定义在普通类中,也可以定义在泛型类中
public void hi(E e){} 不是泛型方法,而是方法使用了类定义的泛型
泛型方法,可以使用类声明的泛型,也可以使用自己声明的泛型
public class GenericM {
public static void main(String[] args) {
Car car = new Car();
//当调用方法时,传入参数,编译器会确定对应的类型
car.fly("宝马", 2);
System.out.println("===========");
car.fly(4.2, "大黄");
}
}
class Car{
public void run(){ } // 普通方法
public <T, R> void fly(T t, R r){
System.out.println(t.getClass());
System.out.println(r.getClass());
} //泛型方法
}
class Plane<T, R>{
public void run(){ } // 普通方法
public <U, V> void fly(U u, V v){} //泛型方法
public <U, V> void walk(U u, V v,T t, R r){} //既使用自己的泛型,也使用类定义的泛型
}
7.泛型的继承和通配符
泛型不具备继承性 List< Object > list = new ArrayList(); (不可以
< ? > 支持任意泛型类型
< ? extends A> 支持A类以及A的子类,规定了泛型的上限
< ? super A> 支持A类以及A的父类,规定了泛型下限
import java.util.ArrayList;
import java.util.List;
public class Generic3 {
public static void main(String[] args) {
A a = new A();
B b = new B();
C c = new C();
ArrayList<Object> list1 = new ArrayList<>();
ArrayList<String> list2 = new ArrayList<>();
ArrayList<A> list3 = new ArrayList<>();
ArrayList<B> list4 = new ArrayList<>();
ArrayList<C> list5 = new ArrayList<>();
printCollection1(list1);
printCollection1(list2);
printCollection1(list3);
printCollection1(list4);
printCollection1(list5);
// printCollection2(list1); //错误
// printCollection2(list2); //错误
printCollection2(list3);
printCollection2(list4);
printCollection2(list5);
printCollection3(list1);
// printCollection3(list2); //错误
printCollection3(list3);
// printCollection3(list4); //错误
// printCollection3(list5); //错误
}
public static void printCollection1(List<?> c){}
// 接受A 和A的子类
public static void printCollection2(List<? extends A> c){}
// 接受A 和A的父类
public static void printCollection3(List<? super A > c){}
}
class A{}
class B extends A {}
class C extends B{}
8.Junit测试框架
package Junit_;
import org.junit.jupiter.api.Test;
public class Junit_ {
public static void main(String[] args) {
// 传统方法
// new Junit_().m1();
// new Junit_().m2();
}
@Test //alt + enter 选5...就可以单独测试方法
public void m1(){
System.out.println("m1被调用");
}
@Test
public void m2(){
System.out.println("m2被调用");
}
}
输入输出流
1.文件
文件流: 文件在程序中是以流的形式来操作的
流:数据在数据源(文件)和程序(内存)之间经理的路径
输入流:数据从数据源(文件)到程序(内存)的路径
输出流:数据从程序(内存)到数据源(文件)的路径
2. File类
File(String path):根据路径构建
File(String path, String name):根据父目录+子路径构建
File(File dir, String name):根据父目录文件+子路径构建
● File类是java.io包中很重要的一个类;
● File类的对象可以表示文件,还可以表示目录(文件夹),在程序中一个File类对象可以代表一个文件或目录;
● File对象可以对文件或目录的属性进行操作,如:文件名、最后修改期、文件大小等;
● File对象无法操作文件的具体数据,即不能直接对文件进行读/写操作。
package File_;
import org.junit.jupiter.api.Test;
import java.io.File;
import java.io.IOException;
public class File_ {
public static void main(String[] args) {
}
@Test
public void creat1(){
String path = "E:\\newFile1.txt";
File file1 = new File(path);
//只在内存中建立了对象还没有和硬盘发生联系
// 只有执行creatNewFile() 方法才能真正在硬盘中创建文件
try {
file1.createNewFile();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Test
public void creat2(){
File parentFile = new File("E:\\");
String fileName = "newFile2.txt";
File file2 = new File(parentFile,fileName);
try {
file2.createNewFile();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Test
public void creat3(){
String parentPath = "E:\\";
String fileName = "newFile3.txt";
File file3 = new File(parentPath,fileName);
try {
file3.createNewFile();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
常用方法
package File_;
import org.junit.jupiter.api.Test;
import java.io.File;
public class FileInformation {
public static void main(String[] args) {
}
@Test
public void info(){
File file = new File("E:\\newFile1.txt");
// getName() 得到文件名字
System.out.println("文件名字"+file.getName());
// getAbsolutePath() 得到绝对路径
System.out.println("文件绝对路径"+file.getAbsoluteFile());
// getParent()得到父级目录
System.out.println("文件父级目录"+file.getParent());
// length()得到文件大小,字节 utf-8 英文字母一个字节,中文汉字三个字节
System.out.println("文件大小"+file.length());
// exists() 是否存在
System.out.println("是否存在"+file.exists());
// isDirectory() 是否目录
System.out.println("是否目录"+file.isDirectory());
}
}
/*
文件名字newFile1.txt
文件绝对路径E:\newFile1.txt
文件父级目录E:\
文件大小14
是否存在true
是否目录false
对目录进行操作
package File_;
import java.io.File;
public class Directory_ {
public static void main(String[] args) {
}
public void m1(){
String directoryPath = "E:\\demo";
File file = new File(directoryPath);
if(file.exists()){
if(file.delete()){
System.out.println("删除成功");
}
else{
System.out.println("删除失败");
}
}
else {
System.out.println("不存在");
}
}
public void m2(){
String directoryPath = "E:\\demo\\a\\b\\c";
File file = new File(directoryPath);
if(file.exists()){
System.out.println("存在");
}
else {
//创建多级目录用mkdirs(),单级目录用mkdir()
if (file.mkdirs()){
System.out.println("创建成功");
}
else{
System.out.println("创建失败");
}
}
}
}
3.IO流原理及流的分类
原理
- I/O 是input/output缩写,IO技术是非常使用的技术,用于处理数据传输,如读写文件,网络通讯
- Java程序中,对于数据的输入输出操作以流(stream)的方式进行
- java.io包下提供了各种”流“类和接口,以获取不同种类的数据,并通过方法输入或输出数据
- 输入(input)读取外部数据(磁盘,光盘、网络、数据库等存储设备的数据)到程序(内存)中
分类
-
按操作数据单位不同分为:字节流(8位)和字符流(字符对应字节,要看编码) ,(字符流效率高<操作文本文件>,字节流操作二进制文件<声音,视频>无损
-
按数据流的流向:输入输出流
-
按流的角色:节点流,处理流/包装流
-
InputStream和OutputStream,Reader和Writer,都是抽象类,IO流类都是以上四个抽象类派生的。
流VS文件
流相当于外卖小哥,帮助我们传输文件
4.文件字节流
InputStream
FileInputStream
细节在代码注释
package InputStream_;
import File_.File_;
import org.junit.jupiter.api.Test;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class FileInputStream_ {
public static void main(String[] args) {
}
@Test
public void readFile01(){
String filePath = "E:\\newFile1.txt";
int readData = 0;
FileInputStream fileInputStream = null;
try {
// 创建FileInputStream对象用于读取文件
fileInputStream= new FileInputStream(filePath);
// 返回-1表示读完
while ((readData = fileInputStream.read())!=-1){
System.out.print((char)readData);
// 读取中文必然出现乱码, 读取字节,而中文由三个字节组成
}
} catch (IOException e) {
throw new RuntimeException(e);
}finally {
try {
fileInputStream.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
// 使用read(byte[] b)读取,提高效率
@Test
public void readFile02(){
String filePath = "E:\\newFile1.txt";
int readLength= 0;
byte[] buf = new byte[8];
FileInputStream fileInputStream = null;
try {
// 创建FileInputStream对象用于读取文件
fileInputStream= new FileInputStream(filePath);
// 如果读取正常,返回实际读取字节数
// 返回-1表示读完
while ((readLength=fileInputStream.read(buf))!=-1){
String str = new String(buf,0,readLength);
System.out.println(str);
}
} catch (IOException e) {
throw new RuntimeException(e);
}finally {
try {
fileInputStream.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
OutputStream
FileOutputStream
两个构造器: new FileOutputStream(filePath); //每执行一次,从头开始写
new FileOutputStream(filePath,boolean);//添加到原有内容的后面
package OutputStream_;
import File_.File_;
import org.junit.jupiter.api.Test;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class FileOutputStream_ {
public static void main(String[] args) {
}
@Test
public void write(){
// 如果文件不存在,则创建文件
String filePath = "E:\\newFile2.txt";
FileOutputStream fileOutputStream = null;
try {
fileOutputStream = new FileOutputStream(filePath,true);
// 写入单个字符
fileOutputStream.write('a');
fileOutputStream.write('\n');
// 写入字符串
String str = "Hello, World";
// str.getBytes() 把字符串转为字节数组
fileOutputStream.write(str.getBytes());
// write(byte[] b, int off, int len)
fileOutputStream.write('\n');
fileOutputStream.write(str.getBytes(),0,3);
} catch (IOException e) {
throw new RuntimeException(e);
}finally {
try {
fileOutputStream.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
文件拷贝
package OutputStream_;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class FileCopy {
public static void main(String[] args) {
// 创建输入流,将文件读入到程序
// 创建输出流,将文件数据,写入指定文件
String srcfilePath = "E:\\photo.webp";
String destfilePath = "E:\\copytPhoto.webp";
FileInputStream fileInputStream = null;
FileOutputStream fileOutputStream = null;
try {
fileInputStream = new FileInputStream(srcfilePath);
fileOutputStream = new FileOutputStream(destfilePath);
byte[] buf = new byte[1024];
int len = 0;
while ((len=fileInputStream.read(buf))!=-1){
fileOutputStream.write(buf);//一定要用这个方法
}
} catch (IOException e) {
throw new RuntimeException(e);
}finally {
if(fileInputStream!=null){
try {
fileInputStream.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
if (fileOutputStream!=null){
try {
fileOutputStream.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
}
5.文件字符流
FileReader
-
new FileReader(File/String)
-
read:每次读取单个字符,返回该字符,如果到文件末尾返回-1
-
read(char []) 批量读取多个字符到数组,返回读取的字符数,末尾返回01
-
new String(char []) 将char[] 转换成String
-
new String(char[] , off, len):将char[]的指定部分转换成String
-
package FileReader_; import org.junit.jupiter.api.Test; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; public class FileReader_ { public static void main(String[] args) { } @Test public void read01(){ String filePath = "E:\\newFile3.txt"; FileReader fileReader = null; int a = 0; try { fileReader = new FileReader(filePath); while ((a=fileReader.read())!=-1){ System.out.print((char)a); } } catch (IOException e) { throw new RuntimeException(e); }finally { try { fileReader.close(); } catch (IOException e) { throw new RuntimeException(e); } } } @Test public void read02(){ String filePath = "E:\\newFile3.txt"; FileReader fileReader = null; int readlen = 0; char buf[] = new char[8]; try { fileReader = new FileReader(filePath); // 返回实际读取的字符数 while ((readlen=fileReader.read(buf))!=-1){ System.out.print(buf); } } catch (IOException e) { throw new RuntimeException(e); }finally { try { fileReader.close(); } catch (IOException e) { throw new RuntimeException(e); } } } }
FileWriter
- new FileWriter(File/String):覆盖模式,
- new FileWriter(File/String, true) 追加模式
- write(int) 写入单个字符
- write(char[]) 写入指定数组
- write(char[], off, len):写入指定数组的指定部分
- write(string) :写入整个字符
- write(string, off, len)写入字符串指定部分
- String类:toCharArray:将String转换成char[]
- FileWriter使用后,必须要关闭(close)或刷新(flush),否则写入不到指定的文件
package FileWriter_;
import org.junit.jupiter.api.Test;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
public class FileWriter_ {
public static void main(String[] args) {
}
@Test
public void writer01(){
String filePath = "E:\\note.txt";
FileWriter fileWriter = null;
char[] chars = {'a','b','c'};
try {
fileWriter = new FileWriter(filePath);
// 1. write(int) 写入单个字符
fileWriter.write('H');
// 2. write(char[]) 写入指定数组
fileWriter.write(chars);
// 3. write(char[], off, len):写入指定数组的指定部分
fileWriter.write("喜羊羊爱吃草".toCharArray(),0,4);
// 4. write(string) :写入整个字符
fileWriter.write("暖羊羊和灰太狼");
// 5. write(string, off, len)写入字符串指定部分
fileWriter.write("北京欢迎你~~~",0,3);
} catch (IOException e) {
throw new RuntimeException(e);
}finally {
try {
fileWriter.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
6.节点流和处理流
节点流
节点流可以从一个特定的数据源读写数据,如FileReader(灵活性差,效率低,跟数据直接相关
处理流
也叫包装流,是”连接“在已存在的流(节点流或处理流)之上,为程序提供了更强大的读写功能,也更加灵活
例:
BufferedReader类中,有属性Reader,即可以封装一个节点流,该节点流可以是任意的,只要是Reader子类( 有构造器new BufferedReader(Reader readre);
BufferedWriter类中,有属性Writer,即可以封装一个节点流,该节点流可以是任意的,只要是Reader子类
节点流和处理流区别和联系
- 节点流是底层流/低级流,直接跟数据源相接
- 处理流包装节点流,可以消除不同节点流的实现差异,也可以提供更方便的方法来完成输入输出(源码理解
- 处理流对节点进行包装,使用了修饰器设计模式,不会直接与数据相连(模拟修饰器设计模式
- 处理流的功能:
性能的提高:主要增加缓冲的方式来提高输入输出的效率
操作的便捷:处理流可能提供了一系列便捷的方法来依次输入输出大批量的数据, 使用更加灵活方便
修饰器模拟:
//Test.java
public class Test {
public static void main(String[] args) {
BufferedReader_ bufferedReader_ = new BufferedReader_(new FileReader());
bufferedReader_.readFiles(5);
// 这次希望对Pipe读取
BufferedReader_ bufferedReader_1 = new BufferedReader_(new PipeReader());
bufferedReader_1.readPipe(5);
}
}
//Reader_.java
public abstract class Reader_ {
public void readFile(){};
public void readPipe(){};
}
//PipeReader.java
public class PipeReader extends Reader_{
public void readPipe(){
System.out.println("读取管道");
}
}
//FileReader.java
public class FileReader extends Reader_{
public void readFile(){
System.out.println("读取文件");
}
}
//BufferedReader_.java
//做成处理流
public class BufferedReader_ extends Reader_{
private Reader_ reader;
BufferedReader_(Reader_ reader_){
reader = reader_;
}
public void readFiles(int num){
for (int i = 0; i<num; i++){
reader.readFile();
}
}
public void readPipe(int num){
for(int i = 0; i < num;i++){
reader.readPipe();
}
}
}
BufferedReader
字符流,按照字符读取数据,关闭时只关闭外层流就可以
package FileReader_;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
public class BufferedReader_ {
public static void main(String[] args) throws Exception {
String filePath = "E:\\newFile3.txt";
BufferedReader bufferedReader = null;
bufferedReader = new BufferedReader(new FileReader(filePath));
String line;
// 按行读取,效率高,返回null时表示读取完毕
while((line = bufferedReader.readLine())!=null){
System.out.println(line);
}
// 只需关闭外层流,即bufferedReader即可,底层会自动关闭FileReader
bufferedReader.close();
}
}
/*
public void close() throws IOException {
synchronized (lock) {
if (in == null)
return;
try {
in.close(); //in就是传进来的内层流
} finally {
in = null;
cb = null;
}
}
BufferedWriter
字符流,按照字符读取数据, 关闭时只关闭外层流就可以
package FileWriter_;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
public class BufferedWriter_ {
public static void main(String[] args) throws IOException {
String filePath = "E:\\ok.txt";
BufferedWriter bufferedWriter = null;
// 想要追加写 FileWriter(String, true)
bufferedWriter = new BufferedWriter(new FileWriter(filePath));
bufferedWriter.write("hello,喜羊羊");
bufferedWriter.newLine(); //插入一个和系统相关的换行
bufferedWriter.write("hello,喜羊羊");
bufferedWriter.newLine();
bufferedWriter.write("hello,喜羊羊");
bufferedWriter.close();
}
}
使用Buffered文件拷贝
package OutputStream_;
import java.io.*;
public class BufferedCopy {
public static void main(String[] args) {
// 创建输入流,将文件读入到程序
// 创建输出流,将文件数据,写入指定文件
String srcfilePath = "E:\\photo.webp";
String destfilePath = "E:\\copytPhoto2.webp";
BufferedInputStream bfi = null;
BufferedOutputStream bfo = null;
try {
bfi = new BufferedInputStream(new FileInputStream(srcfilePath));
bfo = new BufferedOutputStream(new FileOutputStream(destfilePath));
byte[] buf= new byte[1024];
int len = 0;
while ((len=bfi.read(buf))!=-1){
bfo.write(buf);//一定要用这个方法
}
} catch (IOException ex) {
throw new RuntimeException(ex);
}finally {
try {
bfi.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
try {
bfo.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
7.对象处理流
介绍
-
序列化: 保存数据的值和数据类型
-
反序列化:恢复数据的值和数据类型
-
使用对象流必须保证对象是序列化的,且成员对象也必须是序列化
-
序列化是为了保证能把对象写入文件,并能正确度回到程序。
-
为了让对象序列化,需让对象实现Serializable(标记接口,没有方法,常用)或者Externalizable接口
-
ObjectInputStream,ObjectoutputStream中有inputstream和outputstream成员变量,是修饰器类型。构造器
new ObjectInputStream(InputStream)
new ObjectOutputStream(OutputStream)
-
反序列化时,读取数据和存放数据一致
package ObjectStream_;
import java.io.*;
public class ObjectStream_ {
public static void main(String[] args) throws IOException, ClassNotFoundException {
String path = "E:\\objectFile.dat";
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(path));
oos.writeInt(100); //int -》 装箱成Integer(实现Serializable
oos.writeBoolean(true); //boolean -》装箱成Boolean(实现Serializable
oos.writeChar('a'); //char -》 装箱成 Character(实现Serializable
oos.writeUTF("喜羊羊"); //String
oos.writeObject(new Dog("大黄",12));
oos.close();
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(path));
//读取顺序和存放顺序一致
System.out.println(ois.readInt());
System.out.println(ois.readBoolean());
System.out.println(ois.readChar());
System.out.println(ois.readUTF());
Object dog = ois.readObject();
System.out.println("运行类型:"+dog.getClass());
System.out.println("dog信息:"+dog); //底层Object-》dog
// 如果需要调用Dog方法,需要向下转型
// 需要我们将Dog类的定义拷贝到可以引用的位置
Dog dog1 = (Dog)dog;
System.out.println("姓名"+dog1.getName());
ois.close();
}
}
class Dog implements Serializable{
String name;
int age;
public Dog(String name, int age) {
this.name = name;
this.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;
}
@Override
public String toString() {
return "Dog{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
注意
- 读取顺序一致
- serialVersionUID 序列化版本号,可以提高兼容性(当进行序列化时,不会因为添加东西而认为是新的类
- 默认将所有成员序列化,除了static或transient修饰的成员
- 序列化具有可继承性
对象克隆
//Example.java
public class Example {
public static void main(String[] args) {
TV changhongTv = new TV("changhong", 200);
File file = new File("television.txt");
try {
FileOutputStream fileOut = new FileOutputStream(file);
ObjectOutputStream objectOut = new ObjectOutputStream(fileOut);
objectOut.writeObject(changhongTv);
objectOut.close();
FileInputStream fileIn = new FileInputStream(file);
ObjectInputStream objectIn = new ObjectInputStream(fileIn);
TV xinfei = (TV)objectIn.readObject();//此时xinfei和changhong内容一模一样
objectIn.close();
// xinfei.setName("心扉电视");
// xinfei.setPrice(6666);
System.out.println("changhong的名字"+changhongTv.getName());
System.out.println("changhong的价格"+changhongTv.getPrice());
System.out.println("xinfei的名字"+xinfei.getName());
System.out.println("xinfei的名字"+xinfei.getPrice());
} catch (Exception event) {
// TODO: handle exception
}
}
}
//TV.java
import java.io.Serializable;
public class TV implements Serializable{
private String name;
private int price;
public TV(String name, int price) {
// TODO Auto-generated constructor stub
this.name = name;
this.price = price;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
@Override
public String toString() {
// TODO Auto-generated method stub
return "名字"+this.name+"价格"+this.price;
}
}
8.标准输入输出流
system.in
System类的public final static InputStream in = null
System.in 编译类型 InputStream
System.in 运行类型 BufferedInputStream
表示标准输入(键盘
system.out
System类 public final static PrintStream out = null
编译类型 PrintStream
运行类型 PrintStream
表示标准输出(显示器
例:System.out.println(“xyy”) 相当于调用out类的println方法
9.转换流
字节流 《–》 字符流 (引文乱码问题,字符流不能指定编码格式
- InputStreamReader:Reader的子类,可以将InputStream(字节流)包装成Reader(字符流),构造器 new InputStreamReader(InputStream, Charset)
- OutputStreamReader:Writer的子类,实现将OutputStream(字节流)包装成Writer(字符流),构造器 new OutputStreamReader(OutputStream, Charset)
- 当处理纯文本数据,使用字符流效率更高,可以有效解决中文问题,建议将字节流转换成字符流
- 可以在使用时指定编码格式(比如utf-8,gdk,gb2312,ISO8859-1)
InputStreamReader
package InputStreamReader_;
import java.io.*;
public class InputStreamReader_ {
public static void main(String[] args) throws IOException {
// 将FileInputStream转换成字符流,指定编码utf-8
String path = "E:\\newFile1.txt";
InputStreamReader gbk = new InputStreamReader(new FileInputStream(path), "utf-8");
// 把InputStreamReader转换成BufferedReader
BufferedReader bufferedReader = new BufferedReader(gbk);
// 读取
String s = bufferedReader.readLine();
System.out.println(s);
// 关闭外层流
bufferedReader.close();
}
}
OutputStreamWriter
package OutputStreamWriter_;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
public class OutputStreamWriter_ {
public static void main(String[] args) throws IOException {
String path = "E:\\myy.txt";
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(path),"utf-8");
osw.write("美羊羊");
osw.close();
}
}
10 文件锁
上锁
RandomAccessFile input = new RandomAccessFile(file, “rw”);
FileChannel channel = input.getChannel();
FileLock lock = channel.tryLock();
解锁:
lock.release();
import java.nio.channels.*;
import java.util.Scanner;
import java.io.*;
public class Example {
@SuppressWarnings("resource")
public static void main(String[] args) {
File file = new File("C:\\Program Files\\Java\\Eclipse\\Project","xx.txt");
Scanner scanner = new Scanner(System.in);
try {
RandomAccessFile input = new RandomAccessFile(file, "rw");
FileChannel channel = input.getChannel();
FileLock lock = channel.tryLock();
System.out.println("n行");
while(scanner.hasNextInt()) {
int m = scanner.nextInt();
lock.release();
for(int i = 1; i<=m;i++) {
String string = input.readLine();
System.out.println(string);
}
lock = channel.tryLock();
System.out.println("n行");
}
} catch (IOException e) {
// TODO:
handle exception
}
}
}
11.打印流
只有输出流没有输入流
PrinterStream , 字节流, System.setOut()方法设置输出位置(默认为屏幕)
print()方法底层是write()方法
System.setOut(new PrintStream(path));
public static void setOut(PrintStream out) {
checkIO();
setOut0(out); //setOut为本地方法
}
PrintWriter 字符流
PrintWriter printWriter = new PrintWriter(System.out);//默认输出屏幕
PrintWriter printWriter1 = new PrintWriter(new FileWriter("E:\\myy.txt"));//输出到指定路径
printWriter1.write("hi,beijing");
printWriter1.close();//flush+关闭流,才会将数据写入到文件。。
12.Properties类
-
专门用于读写配置文件的集合类 配置文件格式:键=值
-
键值对不需要有空格,值不需要用引号引起来,默认类型String
-
父类是hashtable,核心方法就是hashtable方法
常用方法:
- load:加载配置文件的键值对到Properties对象
- list:将数据显示到指定设备
- getProperty(key):根据键获取值
- setProperty(key,value):设置键值对到对象
- store:将Properties中的键值对存储到配置文件,在idea中,保存信息到配置文件,如果含有中文,会存储问unicode码
package Properties_;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.Properties;
public class Properties01 {
public static void main(String[] args) throws IOException {
Properties properties = new Properties();
properties.load(new FileReader("src\\mysql.properties"));
properties.list(System.out);
//
System.out.println("========");
String name = (String) properties.get("name");
String password = (String) properties.get("password");
System.out.println("用户名是"+name);
System.out.println("密码是"+password);
System.out.println("=========");
}
}
package Properties_;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Properties;
public class Properties02 {
public static void main(String[] args) throws IOException {
Properties properties = new Properties();
properties.setProperty("user","xyy");
properties.setProperty("pwd","2021250");
properties.setProperty("汤姆","250");
properties.setProperty("user","nyy"); //相当于修改
properties.store(new FileOutputStream("src\\mysql2.properties"),null); //null为注释,放在properties文件的最上方
}
}
/*
pwd=2021250
user=xyy
\u6C64\u59C6=250 汤姆的Unicode码
多线程
创建线程两种方式
- 继承Thread类,重写run()方法(Thread类实现了Runnable接口
- 实现Runnable接口,重写run()方法
线程同步–synchronized
多线程编程中,一些敏感数据不允许被多个线程同时访问,此时就使用同步访问计数,保证数据在任何同一时刻,最多有一个线程访问,以保证数据完整性
//1
synchronized(对象){
//需要被同步的代码
}
//2
public synchronized void m(String name){
//需要被同步的代码
}
例: 展示创建线程两种方式 及 线程同步方法 及 互斥锁
package 多线程售票;
public class SellTickets {
public static void main(String[] args) {
//01
// SellTickets01 sellTickets01 = new SellTickets01();
// SellTickets01 sellTickets02 = new SellTickets01();
// SellTickets01 sellTickets03 = new SellTickets01();
// sellTickets01.start();
// sellTickets02.start();
// sellTickets03.start();
//02
// SellTickets02 sellTickets01 = new SellTickets02();
// Thread thread1 = new Thread(sellTickets01);
// Thread thread2 = new Thread(sellTickets01);
// Thread thread3 = new Thread(sellTickets01);
// thread1.start();
// thread2.start();
// thread3.start();
//03
SellTickets03 sellTickets3 = new SellTickets03();
new Thread(sellTickets3).start();//第 1 个线程-窗口
new Thread(sellTickets3).start();//第 2 个线程-窗口
new Thread(sellTickets3).start();//第 3 个线程-窗口
}
}
class SellTickets01 extends Thread{
private static int tickets = 100;
public void run() {
while(true) {
if(tickets < 0) {
System.out.println("已售完");
break;
}
try {
Thread.sleep(500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("窗口"+Thread.currentThread().getName()
+"售出一张票,剩余票数"+(tickets--));
}
}
}
class SellTickets02 implements Runnable{
private static int tickets = 100;
public void run() {
while(true) {
if(tickets < 0) {
System.out.println("已售完");
break;
}
try {
Thread.sleep(500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("窗口"+Thread.currentThread().getName()
+"售出一张票,剩余票数"+(tickets--));
}
}
}
class SellTickets03 implements Runnable{
private static int tickets = 50;
private static boolean loop = true;
public void sell() {
synchronized (this) {
if(tickets < 0) {
System.out.println("已售完");
loop = false;
return;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("窗口"+Thread.currentThread().getName()
+"售出一张票,剩余票数"+(tickets--));
}
}
public void run() {
while(loop) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
sell();
}
}
}
线程常用方法
setName 设置名称
getName 得到名称
start 线程开始执行
run 调用run方法
setPriority 设置线程优先级
getPriority 得到线程优先级
sleep 休眠
interrupt 中断线程 (中断休眠
线程中断例子
package Thread01;
public class 线程中断 {
public static void main(String[] args) throws InterruptedException {
Eat eat = new Eat();
eat.setName("大黄");
eat.setPriority(Thread.MIN_PRIORITY);
eat.start();
for(int i = 0; i < 5; i++) {
Thread.sleep(1000);
System.out.println("hi" + i);
}
eat.interrupt();//提前终止休眠
}
}
class Eat extends Thread{
@Override
public void run() {
while(true) {
for(int i = 0; i<50; i++) {
System.out.println(Thread.currentThread().getName()+"吃包子");
}
try {
System.out.println(Thread.currentThread().getName()+"睡觉");
Thread.sleep(10000);
}catch (InterruptedException e) {
// TODO: handle exception
System.out.println(Thread.currentThread().getName()+"被interrupt");
}
}
}
}
线程终止
1 线程完成任务自动退出
2 通过使用变量控制run方法推出停止线程,即通知方式
package Thread01;
import java.util.Iterator;
public class 线程终止 {
public static void main(String[] args) {
A a = new A();
new Thread(a).start();
for (int i = 0; i < 20 ;i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("main执行");
if(i == 10) {
a.setLoop(false);
}
}
}
}
class A implements Runnable{
private boolean loop = true;
@Override
public void run() {
// TODO Auto-generated method stub
while(loop) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("线程A正在运行");
}
}
public void setLoop(boolean loop) {
this.loop = loop;
}
}
守护线程
将一个线程设为守护线程,当所有线程结束时该线程也结束
setDaemon() 方法设为守护线程
package 守护线程;
public class ProtectThread {
public static void main(String[] args) throws InterruptedException {
ProtectThread0 protectThread0 = new ProtectThread0();
protectThread0.setDaemon(true);
protectThread0.start();
for(int i = 0; i < 20; i++) {
Thread.sleep(1000);
System.out.println("小兰在和小红聊天");
}
}
}
class ProtectThread0 extends Thread{
public void run() {
while(true) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("小明在和小红聊天");
}
}
}
线程插队
yield:线程的礼让,让出cup,让其他线程执行,但礼让事件不确定,所有不一定礼让成功
join:线程的插队,插队的线程一旦成功,则肯定先执行完插入的线程所有的任务
package Thread01;
import java.util.Iterator;
public class 线程插队 {
public static void main(String[] args) throws InterruptedException {
Hello hello = new Hello();
Thread thread = new Thread(hello);
thread.start();
for(int i = 0; i < 10; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("Hi");
if(i == 5) {
System.out.println("主线程让出");
// join()方法
// thread.join();
// yield()方法,不一定成功
Thread.yield();
}
}
}
}
class Hello implements Runnable{
@Override
public void run() {
// TODO Auto-generated method stub
for(int i = 0; i < 10; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("Hello");
}
}
}
互斥锁
- java语言中,引入对象互斥锁概念,保证共享数据操作的完整性
- 每个对象对应于一个可成为“互斥锁”的标记,这个标记来保证在任意时刻,只能有一个线程访问该对象
- 关键字synchronized来与对象的互斥锁联系,当某个对象用该关键字修饰时,表明该对象在任意时刻只能由一个线程访问(要求是同一个对象
- 同步的局限性:导致程序的执行效率降低
- 同步方法(非静态的)的锁可以是this,也可以是其他对象
- 同步方法(静态的)的锁为当前类本身
线程死锁
多个线程都占用了对方的锁资源,,但不肯相让,导致死锁
package Thread01;
public class 死锁 {
public static void main(String[] args) {
DeadLockDemo a = new DeadLockDemo(true);
DeadLockDemo b = new DeadLockDemo(false);
a.setName("A");
b.setName("B");
a.start();
b.start();
}
}
class DeadLockDemo extends Thread{
static Object o1 = new Object();
static Object o2 = new Object();
boolean flag;
public DeadLockDemo(boolean flag) {
this.flag = flag;
}
@Override
public void run() {
// TODO Auto-generated method stub
// 如果flag == true 线程A会先得到o1对象锁,然后尝试获取o2对象锁
// 如果得不到,就会Blocked
// 如果flag ==false 线程B会先得到o2对象锁,然后尝试获取o1对象锁
// 如果得不到,就会Blocked
if(flag) {
synchronized (o1) {
System.out.println(Thread.currentThread().getName()+"进入1");
synchronized (o2) {
System.out.println(Thread.currentThread().getName()+"进入2");
}
}
}else {
synchronized (o2) {
System.out.println(Thread.currentThread().getName()+"进入3");
synchronized (o1) {
System.out.println(Thread.currentThread().getName()+"进入4");
}
}
}
}
}
释放锁
下面操作会释放锁
- 当前线程的同步方法、同步代码块执行结束(案例:上完厕所出来
- 当前线程在同步代码块、同步方法中遇到break,return(没有正常完事,经理叫他出来修bug,不得已出来
- 当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致异常结束(没有正常完事,发现没带纸,不得已出来
- 当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线程暂停,并释放锁(没有正常完事,需要酝酿下,所以出来等会再进去
下面操作不会释放锁
- 线程执行同步代码块或同步方法时,调用sleep(), yield()方法暂停当前线程的执行,不会释放锁(上厕所太困了眯了一会
- 线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起
- 尽量避免suspend()和resume()来控制线程
线程的生命周期
getState() 方法可以查看线程状态
案例
在main方法中启动两个线程,第一个线程随即打印出1-100的数字,第二个线程输入Q结束第一个线程
package 线程例题;
import java.util.Scanner;
public class Example {
public static void main(String[] args) {
A a = new A();
B b = new B(a);
a.start();
b.start();
}
}
class A extends Thread{
private boolean loop = true;
public void run() {
while(loop) {
System.out.println((int)(Math.random()*100)+1);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public void setLoop(boolean loop) {
this.loop = loop;
}
}
class B extends Thread{
A a;
Scanner scanner = new Scanner(System.in);
public B(A a) {
this.a = a;
}
public void run() {
while(true) {
while(true) {
System.out.println("请输入字母(Q)结束线程");
char key = scanner.next().toUpperCase().charAt(0);
if( key == 'Q') {
a.setLoop(false);
System.out.println("B线程结束");
break;
}
}
}
}
}
反射
1. 反射机制
入门
根据配置文件指定信息,创建Cat对象并调用方法hi
通过外部文件,在不修改源码的情况下来控制程序,也符合设计模式的ocp原则(开闭原则)
反射是框架的灵魂
需要改变调用的方法只需改变外部文件(将method = hi 改为method=cry
package Reflection_;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Properties;
public class ReflectionProblem {
public static void main(String[] args) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
Properties properties = new Properties();
properties.load(new FileReader("src\\re.properties"));
String classpath = properties.getProperty("classfullpath").toString();
String methodName = properties.getProperty("method").toString();
// 使用反射机制解决问题
// 1.加载类,返回Class类型的对象cls
Class cls = Class.forName(classpath);
// 2.通过cls得到加载的类Cat的实例对象
Object obj = cls.newInstance();
System.out.println("obj的运行类型"+obj.getClass());
// 3.通过cls得到加载的类Cat的methodName“hi”的方法对象
// 在反射机制中,把方法视为对象(万物皆对象)
Method method = cls.getMethod(methodName);
// 通过method调用方法,即通过方法对象调用方法
System.out.println("-============-");
method.invoke(obj); //方法.invoke(对象)
// 没有反射就没有框架
}
}
package Reflection_;
public class Cat {
private String name = "大黄";
public int age = 10;
public Cat(){
System.out.println("无参构造器");
}
public Cat(String name){
System.out.println("有参构造器");
}
public void hi(){
System.out.println("喵喵喵");
}
public void cry(){
System.out.println("小猫哭了");
}
}
/*
re.properties文件内容
classfullpath=Reflection_.Cat
method=cry
反射机制
-
反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息(比如成员变量,构造器,成员方法)并能操作对象的属性及方法。反射在设计模式和框架底层会用到
-
加载完类之后,在堆中产生一个Class类型的对象(一个类只有一个Class对象),这个对象包含了类的完整结构信息,通过这个对象得到类的结构,
-
-
运行时判断任意一个对象所属的类
-
运行时构造任意一个类的对象
-
运行时得到任意一个类所具有的成员变量和方法
-
运行时调用一个对象的成员变量和方法
-
生成动态代理
反射相关类
这些类在java.lang.reflect包
java.lang.Class 代表一个类,Class对象表示某个类加载后在堆中的对象
java.lang.reflect.Method 代表类方法,Method对象表示某个类的方法
java.lang.reflect.Field 代表类的成员变量,Field对象表示某个类的成员变量
java.lang.reflect.Constructor 代表类的构造方法。Constructor表示类的构造器
// java.lang.reflect.Constructor: 代表类的构造方法, Constructor 对象表示构造器
Constructor constructor = cls.getConstructor();
//()中可以指定构造器参数类型, 返回无参构造器
System.out.println(constructor);
//Cat()
Constructor constructor2 = cls.getConstructor(String.class); //这里老师传入的 String.class 就是 String 类的 Class 对象
System.out.println(constructor2);//Cat(String name)
优缺点
优点:可以动态创建和适用对象(也是框架底层核心),使用灵活,没有反射机制,框架就失去底层支撑
缺点:使用反射基本是解释执行,队速度有影响
优化
Method,Fiedl,Constructor对象都有setAccessible()方法
作用是启动和禁用访问安全检查的开关
参数值为true表示 反射的对象在使用时取消访问检查,提高反射效率。参数值为false则表示反射对象执行访问检查
package Reflection_;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class Reflection2 {
public static void main(String[] args) throws ClassNotFoundException, InvocationTargetException, NoSuchMethodException, IllegalAccessException, InstantiationException {
m1();
m2();
m3();
}
public static void m1() {
Cat cat = new Cat();
long starttime = System.currentTimeMillis();
for (int i = 0; i < 10000000; i++) {
cat.cry();
}
long endtime = System.currentTimeMillis();
System.out.println("m1:" + (endtime - starttime));
}
public static void m2() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException {
Class cls = Class.forName("Reflection_.Cat");
Object obj = cls.newInstance();
Method method = cls.getMethod("cry");
long starttime = System.currentTimeMillis();
for (int i = 0; i < 10000000; i++) {
method.invoke(obj);
}
long endtime = System.currentTimeMillis();
System.out.println("m2:" + (endtime - starttime));
}
// 反射优化 关闭访问检查
public static void m3() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException {
Class cls = Class.forName("Reflection_.Cat");
Object obj = cls.newInstance();
Method method = cls.getMethod("cry");
method.setAccessible(true);
long starttime = System.currentTimeMillis();
for (int i = 0; i < 10000000; i++) {
method.invoke(obj);
}
long endtime = System.currentTimeMillis();
System.out.println("m3:" + (endtime - starttime));
}
}
/*
无参构造器
m1:3
无参构造器
m2:109
无参构造器
m3:77
2.Class类
-
Class也是类,继承Object类
-
Class类对象不是new出来的,而是系统创建的
-
对于某个类的Class对象,在内存中只有一份,因为类只加载一次
-
每个类的实例都会记得自己是由哪个Class实例所生产
-
通过Class对象可以完整的得到一个类的完整结构,一系列API
-
Class对象是放在堆的
-
类的字节码二进制数据,是放在方法区的,有的地方称为类的元数据
常用方法
package Class_;
import Reflection_.Car;
import java.lang.reflect.Field;
public class Class02 {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchFieldException {
String classpath = "Reflection_.Car";
// <?>表示不确定的Java类型
Class<?> cls = Class.forName(classpath);
// 获得哪个类的Class对象
System.out.println(cls);
// 获得运行类型
System.out.println(cls.getClass());
// 得到包的名字
System.out.println(cls.getPackageName());
// 得到全类名
System.out.println(cls.getName());
// 创建对象实例
Car car = (Car)cls.newInstance();
System.out.println(car);
// 通过反射获得属性
Field field = cls.getField("name");
System.out.println(field.get(car));
// 通过反射给属性赋值
field.set(car,"奔驰");
System.out.println(field.get(car));
// 得到所有的属性
Field[] fields = cls.getFields();
System.out.println("====所有字段属性====");
for (Field field1 :fields) {
System.out.println(field1.getName());
}
}
}
/*
class Reflection_.Car
class java.lang.Class
Reflection_
Reflection_.Car
Car{name='宝马', price=10000, color='蓝色'}
宝马
奔驰
====所有字段属性====
name
price
color
获取Class对象六种方式
-
编译阶段:Class.forName()
-
Class类加载阶段:类.class
-
运行阶段:对象.getClass()
-
类加载器:
-
Class.forName():
前提:已知一个类的全类名,且该类在类路径下,可通过Class类的静态方法forName()获取,可能抛出ClassNotFoundException,实例:Class.forName(Reflection_.Cat)
应用场景:多用于配置文件,读取类全路径,加载类
- 类名.class:
前提:已知具体的类,通过类的class获取,该方法最为安全可靠,程序性能最高,实例:Class cls2 = Cat.class
应用场景:多用于参数传递
- 对象.getClass():
前提:已知某个对象的实例,调用该实例的getClass()方法获取,实例:Class cls = 对象.getClass()
应用场景:通过创建好的对象,获取Class对象
- 类加载器:
ClassLoader classLoader = cls3.getClass().getClassLoader();
Class<?> cls4 = classLoader.loadClass(“Reflection_.Car”);
- 基本数据类型:
Class cls = 基本数据类型.class
-
基本数据类型对应包装类:
Class cls = 包装类.TYPE
package Class_;
import Reflection_.Car;
public class GetClass_ {
public static void main(String[] args) throws ClassNotFoundException {
// 1
Class cls = Class.forName("Reflection_.Car");
System.out.println(cls);
// 2
Class cls2 = Car.class;
System.out.println(cls2);
// 3
Car cls3 = new Car();
System.out.println(cls3.getClass());
// 4通过类加载器
// (1)获取类加载器
ClassLoader classLoader = cls3.getClass().getClassLoader();
// (2)通过类加载器得到Class对象
Class<?> cls4 = classLoader.loadClass("Reflection_.Car");
System.out.println(cls4);
// 5基本数据类型
Class<Integer> integerClass = int.class;
System.out.println(integerClass);
// 6.基本数据类型的包装类型
Class<Integer> type = Integer.TYPE;
System.out.println(type);
Class<Character> type1 = Character.TYPE;
System.out.println(type1);
// 判断相等否,java底层自动装箱拆箱,是相等的
System.out.println(integerClass.hashCode());
System.out.println(type.hashCode());
}
}
/*
class Reflection_.Car
class Reflection_.Car
class Reflection_.Car
class Reflection_.Car
int
int
char
589431969
589431969
那些类有Class对象
- 外部类,成员内部类,静态内部类,局部内部类,匿名内部类
- interface接口
- 数组
- enum枚举
- annotation注解
- 基本数据类型
- void
package Class_;
import java.io.Serializable;
public class AllTypeClass {
public static void main(String[] args) {
Class<String> c1 = String.class;
Class<Serializable> c2 = Serializable.class;
Class<Integer[]> c3 = Integer[].class;
Class<float[][]> c4 = float[][].class;
Class<Deprecated> c5 = Deprecated.class;
Class<Thread.State> c6 = Thread.State.class;
Class<Long> c7 = long.class;
Class<Void> c8 = void.class;
Class<Class> c9 = Class.class;
System.out.println(c1);
System.out.println(c2);
System.out.println(c3);
System.out.println(c4);
System.out.println(c5);
System.out.println(c6);
System.out.println(c7);
System.out.println(c8);
System.out.println(c9);
}
}
/*
class java.lang.String
interface java.io.Serializable
class [Ljava.lang.Integer;
class [[F
interface java.lang.Deprecated
class java.lang.Thread$State
long
void
class java.lang.Class
3.类加载
静态加载和动态加载
静态加载:编译时加载相关的类,如果没有则报错,依赖性太强
动态加载:运行时加载相关的类,如果运行时不用该类,则不会报错,降低了依赖性
类加载时机:
- 创建对象时 //静态加载
- 子类被加载时 //静态加载
- 调用类中的静态方法 //静态加载
- 通过反射 //动态加载
类加载过程
加载:JVM在该阶段主要目的时将字节码从不同的数据源(可能是class文件、也可能是jar包,网络)转化为二进制字节流加载到内存中(方法区)并生成应该代表该类的java.lang.Class对象
验证:目的是确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全
包括:文件格式验证(是否以魔数oxcafebabe开头),元数据验证,字节码验证和符号引用验证
可以考虑使用 -Xverify:none参数来关闭大部分的类验证措施,缩短虚拟机类加载的时间
准备:JVM对静态变量进行默认初始化并分配空间(对应默认初始值,如0,0L,null,false等)这些变量所使用的内存都将在方法区进行分配
class A{
//a1是实例属性,不是静态变量,因此在准备阶段不会分配内存
//a2是静态变量,分配内存a2 是默认初始化0, 而不是20
//a3是static final 是常量,他和静态变量不一样,会一次性分配,因为一旦赋值就不变,a3直接为30
public int a = 10;
public static int a1 = 20;
public static final int a3 = 30;
}
解析:JVM将常量内的符号引用()转为直接引用(内存地址)
初始化:真正开始执行类中定义的Java程序代码,此阶段是执行< clinit >()方法的过程
< clinit>()方法是由编译器按语句在源文件中出现的顺序,依次自动收集类中所有静态变量的复制动作和静态代码块中的语句,并进行合并
虚拟机会保证一个类的 clinit()方法在多线程环境中被正确的枷锁,同步,如果多个线程同时去初始化一个类,那么只有一个线程去执行类的clinit()方法,其他线程都需阻塞等待,知道活动线程执行完毕
public class ClassLoad_ {
public static void main(String[] args) {
// 1.加载A类,并生成A的class对象
// 2.链接 a=0
// 3.初始化阶段
// 依次收集静态变量的复制动作和静态代码块中的语句,并合并
/* clinit(){
静态代码块
a = 300;
a = 3;
}
合并 a =3;
A构造器
*/
A a = new A();
System.out.println(A.a);
}
}
class A{
static {
System.out.println("静态代码块");
a = 300;
}
public static int a = 3;
A(){
System.out.println("A的构造器");
}
}
/*
静态代码块
A的构造器
3
4.获取类的相关信息
Class常用API
1.getName:获取全类名
2.getSimpleName:获取简单类名
3.getFields:获取所有public修饰的属性,包含本类以及父类的
4.getDeclaredFields:获取本类中所有属性
5.getMethods:获取所有public修饰的方法,包含本类以及父类的
6.getDeclaredMethods:获取本类中所有方法
7.getConstructors::获取本类所有publicf修饰的构造器
8.getDeclaredConstructors:获取本类中所有构造器
9.getPackage:以Package形式返回包信息
10.getSuperClass:以Classj形式返回父类信息
11.getInterfaces:l以Class[]形式返回接回信息
12.getAnnotations:以Annotation[]形式返回注解信息
package Reflection_;
import org.junit.jupiter.api.Test;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class ReflectionUtils_ {
public static void main(String[] args) {
}
@Test
public void test1() throws ClassNotFoundException {
Class cls = Class.forName("Reflection_.Person");
// 1.getName:获取全类名
System.out.println(cls.getName());
// 2.getSimpleName:获取简单类名
System.out.println(cls.getSimpleName());
// 3.getFields:获取所有oublic修饰的属性,包含本类以及父类的
Field[] fields = cls.getFields();
for (Field field : fields) {
System.out.println("父类及本类的所有public属性"+field.getName());
}
// 4.getDeclaredFields:获取本类中所有属性
Field[] fields1 = cls.getDeclaredFields();
for (Field field : fields1) {
System.out.println("父类及本类的所有属性"+field.getName());
}
// 5.getMethods:获取所有public修饰的方法,包含本类以及父类的
Method[] methods = cls.getMethods();
for (Method method : methods) {
System.out.println("本类及父类的所有public方法"+method.getName());
}
// 6.getDeclaredMethods:获取本类中所有方法
Method[] methods1 = cls.getDeclaredMethods();
for (Method method : methods1) {
System.out.println("本类及父类的所有方法"+method.getName());
}
// 7.getConstructors::获取本类所有publicf修饰的构造器
Constructor[] constructors = cls.getConstructors();
for (Constructor constructor : constructors) {
System.out.println("本类的public构造器"+constructor.getName());
}
// 8.getDeclaredConstructors:获取本类中所有构造器
// 9.getPackage:以Package形式返回包信息
System.out.println(cls.getPackage());
// 10.getSuperClass:以Class形式返回父类信息
Class superClass = cls.getSuperclass();
System.out.println(superClass);
// 11.getInterfaces:l以Class[]形式返回接回信息
Class[] interfaces = cls.getInterfaces();
for (Class anInterface : interfaces) {
System.out.println("接口信息"+anInterface);
}
// 12.getAnnotations:以Annotation[]形式返回注解信息
Annotation annotations[] = cls.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println("注解信息"+annotation);
}
}
}
class A{
public String hobby;
public A(){}
public void hi(){
}
}
interface IA{
}
interface IB{
}
@Deprecated
class Person extends A implements IA,IB{
private String name;
protected int age;
String job;
public int salary;
public Person(){
}
public void m1(){
}
protected void m2(){
}
void m3(){
}
public void m4(){
}
}
Field类常用API
- getModifiers()以int形式返回修饰符,默认是0,public 是 1, private 是 2,protected 是 4, static 是 8,final 是 16,public final是 public(1)+final(16)
- getType得到该属性的类型,以Class形式获取,返回数组
- getName返回属性名
Method类常用API
- getModifiers()以int形式返回修饰符,默认是0,public 是 1, private 是 2,protected 是 4, static 是 8,final 是 16,public final是 public(1)+final(16)
- getReturnType得到该属性的类型,以Class形式获取,返回数组
- getName返回属性名
- getParameterTypes,以Class[]返回参数类型数组
Constructor类
- getModifiers()以int形式返回修饰符,默认是0,public 是 1, private 是 2,protected 是 4, static 是 8,final 是 16,public final是 public(1)+final(16)
- getType得到该属性的类型,以Class形式获取,返回数组
- getParameterTypes 以Class类型返回参数类型数组
package Reflection_;
import org.junit.jupiter.api.Test;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.time.Period;
public class ReflectionUtils_ {
public static void main(String[] args) {
}
@Test
public void test2() throws ClassNotFoundException{
Class cls = Class.forName("Reflection_.Person");
// 4.getDeclaredFields:获取本类中所有属性
Field[] fields1 = cls.getDeclaredFields();
for (Field field : fields1) {
System.out.println("父类及本类的所有属性\t"+field.getName()+"属性修饰符\t"
+field.getModifiers()+"属性类型\t"+field.getType());
}
Method[] methods1 = cls.getDeclaredMethods();
for (Method method : methods1) {
System.out.println("============");
System.out.println("本类及父类的所有方法\t"+method.getName()+"属性修饰符\t"
+method.getModifiers()+"属性类型\t"+method.getReturnType()
+"");
Class[] parameterTypes = method.getParameterTypes();
for (Class parameterType : parameterTypes) {
System.out.println("该方法的形参"+parameterType);
}
}
Constructor[] constructors = cls.getDeclaredConstructors();
for (Constructor constructor : constructors) {
System.out.println("============");
System.out.println("本类的构造器"+constructor.getName());
Class[] parameterTypes = constructor.getParameterTypes();
for (Class parameterType : parameterTypes) {
System.out.println("构造器形参"+parameterType);
}
}
}
class A{
public String hobby;
public A(){}
public void hi(){
}
}
interface IA{
}
interface IB{
}
@Deprecated
class Person extends A implements IA,IB{
private String name;
protected int age;
String job;
public int salary;
private Person(double b){
}
public Person(String name, int age){
}
public void m1(String name, int age, double salary){
}
protected String m2(){
return null;
}
void m3(){
}
public void m4(){
}
}
/*
父类及本类的所有属性 name属性修饰符 2属性类型 class java.lang.String
父类及本类的所有属性 age属性修饰符 4属性类型 int
父类及本类的所有属性 job属性修饰符 0属性类型 class java.lang.String
父类及本类的所有属性 salary属性修饰符 1属性类型 int
============
本类及父类的所有方法 m2属性修饰符 4属性类型 class java.lang.String
============
本类及父类的所有方法 m1属性修饰符 1属性类型 void
该方法的形参class java.lang.String
该方法的形参int
该方法的形参double
============
本类及父类的所有方法 m3属性修饰符 0属性类型 void
============
本类及父类的所有方法 m4属性修饰符 1属性类型 void
============
本类的构造器Reflection_.Person
构造器形参double
============
本类的构造器Reflection_.Person
构造器形参class java.lang.String
构造器形参int
进程已结束,退出代码0
5.反射暴破
反射暴破创建实例
Class类相关方法
new Instance:调用public无参构造器,获取对象
getConstructor() 根据参数列表,获取对应的public构造器
getDecalaredConstructor() 根据参数列表获取对应构造器
Constructor类相关方法
setAccessible:暴破,使反射可以调用私有方法
newInstancce()调用构造器
例:
package Reflection_;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class CreateClass {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
// 获取到User类的对象
Class cls = Class.forName("Reflection_.User");
// 无参
Object o = cls.newInstance();
System.out.println(o);
// public有参
// 此时constructor就是
// public User(String name) {//public的有参构造器
// this.name = name;
// }
Constructor constructor = cls.getConstructor(String.class);
Object m = constructor.newInstance("美羊羊");
System.out.println(m);
// 非public有参
Constructor constructor1 = cls.getDeclaredConstructor(int.class, String.class);
// 添加暴破,使用反射可以使用private构造器
constructor1.setAccessible(true);
Object n = constructor1.newInstance(18, "暖羊羊");
System.out.println(n);
}
}
class User {
private int age = 10;
private String name = "喜羊羊";
public User() {//无参public
}
public User(String name) {//public的有参构造器
this.name = name;
}
private User(int age, String name) {//private有参构造器
this.age = age;
this.name = name;
}
public String toString() {
return "User [age=" + age + "name=" + name + "]";
}
}
/*
User [age=10name=喜羊羊]
User [age=10name=美羊羊]
User [age=18name=暖羊羊]
操作类中属性
Field f = clazz对象.getDeclaredField(属性名)
暴破:f.setAccessible(true)
访问:f.set(o, 值) f.get(o) o表示对象 若是静态属性,则o可以写成null
package Reflection_;
import java.lang.reflect.Field;
public class Accessproperty {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchFieldException {
Class cls = Class.forName("Reflection_.Student");
Object o = cls.newInstance();//o的运行类型已经是Student
// 使用反射操作age
Field age = cls.getField("age");
age.set(o, 18);//通过反射操作属性
System.out.println(o);
System.out.println(age.get(o));
// 使用反射操作name
Field name= cls.getDeclaredField("name");
name.setAccessible(true);
// name.set(o,"暖羊羊");
name.set(null,"暖羊羊");
System.out.println(o);
System.out.println(name.get(null));
}
}
class Student{
public int age;
private static String name;
public Student(){
}
@Override
public String toString() {
return "Student{" +
"age=" + age +"name="+name+
'}';
}
}
/*
Student{age=18name=null}
18
Student{age=18name=暖羊羊}
暖羊羊
操作类中方法
暴破访问:method.setAccessble(true) 暴破方法
访问:Object returnValue = method.invoke(o, 实参列表)//o就是对象
有返回值,统一返回Object类型,运行类型是method方法类型
如果是静态方法,o可以是null
package Reflection_;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class AccessMethod {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
Class cls = Class.forName("Reflection_.Boss");
Object o = cls.newInstance();
Method hi = cls.getMethod("hi", String.class);
hi.invoke(o, "喜羊羊");
Method say = cls.getDeclaredMethod("say",int.class,String.class,char.class);
say.setAccessible(true);
System.out.println(say.invoke(o,17,"沸羊羊",'s'));
// 因为是static方法,故o可以是null
System.out.println(say.invoke(null,200,"红太狼",'l'));
}
}
class Boss{
public int age;
private static String name;
public Boss(){
}
private static String say(int age, String name,char c ){
return name+" "+age+" "+c;
}
public void hi(String s){
System.out.println("hi"+s);
}
}
/*
hi喜羊羊
沸羊羊 17 s
红太狼 200 l