javaSE重点,
javaSE重点,
继承(day01)
注意 : 子类重写父类方法时,不能使用比父类中被重写的方法更严格的访问权限.
public > protected > 默认 > private
重写父类方法 (override) overload 重载
在继承关系中,子类会自动继承父类中定义的方法,但有时在子类中需要对继承的方法进行一些修改.即对父类的方法进行重写.需要注意的是,在子类中重写的方法需要和父类被重写的方法具有相同的方法名,参数列表以及返回值类型.
在使用new关键字创建对象的时候,先在堆中给对象分配内存空间,接着给成员变量进行默认初始化,返回该空间的首地址, 然后开始调用对应的构造方法。
构造方法中的隐式 : super(); 它是在调用自己的父类空参数的构造方法。
当用一个子类的构造创建一个类的对象时,子类的构造方法总是先调用父类的某个构造方法,也就是说,如果子类的构造方法没有明显地指明使用父类的哪个构造方法子类就调用父类的不带参数的构造方法. 由于子类不继承父类的构造方法,因此子类在其构造方法中需要使用 super 来调用父类的构造方法,而且 super 必须是子类构造方法中的头一条语句
说明 : 使用 super(参数列表); 实现调用父类的的构造方法让父类为自己的成员属性赋值.
多态(day02)
多态概述 :
在设计一个方法时,通常希望该方法具备一定的通用性.在同一个方法中,这种由于参数类型不同而导致执行效果各异的现象就是多态. 在Java中为了实现多态,允许使用一个父类类型的变量来引用一个子类类型的对象,根据被引用子类对象特征的不同,得到不同的运行结果
多态性 : 多态的基础是继承. 类之间先有继承关系之后,才会产生多态的形式.
多态 : 同一个行为,对于传入不同的 对象
而言.具有完全不同的表现形式.
多态语句在代码中的体现 : 父类的引用指向了子类的对象.
多态的使用场景 :
1. 如果方法的参数是一个父类类型的, 我们传递一个子类对象, 当然是可以的, 因为子类就是一个父类.这样的设计简化了程序的编写.
2. 程序中如果创建了一个子类对象, 我们可以使用父类的引用进行接收, 程序编译不受影响. 提高了代码的灵活性.
在多态中的类型转换问题 :
1. 隐式的类型提升.只要有多态就会发生类型提升. (向上转型) Person p = new Doctor();
2. 强制类型转换. 将父类类型转成子类类型. (向下转型) Doctor d = (Doctor)p;
说明 : 向下转型,目的就是为了调用子类特有的方法.
格式 : 子类类型 子类对象名 = (子类类型)父类对象名;
说明 : 如果调用的是父类中的共性方法,无需进行类型转换. 子类如果有重写,调用子类该方法,子类如果没有进行重写,则调用父类该方法. 需要注意的是,此时不能通过父类变量去调用子类中的 子类特有方法
.
什么时候使用向下转型 :
只要在程序中我们需要使用子类的特有属性或行为的时候,才会使用向下转型.
向下转型的风险
问题 : 向下转型有风险,使用需谨慎. 在Java中要使用向下转型,必须先做类型的判断,然后再转型.Java中的类型判断,需要使用关键字 instanceof.
instanceof 运算符是双元运算符,左面的操作是一个对象,右面是一个类.当左面的对象是右面的类或子类创建的对象时,该运算符运算的结果是true,否则是false.
一定要判断,防止类型转换异常的发生. 如果程序发生 classCastException, 一定是将不是这种类型的对象转成了该类型对象,发生类型转换异常.
final:
final 关键字是用于修饰类,变量和方法,它有”这是无法改变的”或者”最终”的含义.
特点 :
final 修饰的类不能被继承.即不能有子类.
final 修饰的方法不能被子类重写.老老实实继承,不允许做任何篡改.
final 修饰的变量(成员变量和局部变量)是常量,由于常量在运行期间不允许再发生改变,所以常量在声明时没有默认值,这就要求程序在声明常量时必须指定该常量的值.
由于final修饰的变量是常量,我们开发中为了和变量名有区别,因此所有的被final修饰的变量名统一大写。
抽象类:
1、抽象类一定是父类吗?
是的,因为抽象类是不断向上抽取而来的. 一定是父类,但不一定是顶层父类。
抽象类中通常都有抽象方法,而抽象类中的抽象方法要求必须由子类来重写(由具体的子类来实现其中的方法体)。
子类的主要作用 : 重写抽象类中的全部抽象方法。
2、抽象类可以继承其他类吗?
抽象类还是一个类,因此它必须具备类的继承特点。它可以有父类。
3、由于抽象类不能实例化对象,那么请问抽象类是否有构造方法 ?
有构造方法。但是这个类不能创建对象。因为抽象类一定有子类,而创建子类对象的时候,在子类的构造方法中有super语句会找自己父类的构造方法进行初始化动作。所以抽象类的构造方法是用来给子类进行初始化的.
4、抽象类中可以没有抽象方法吗?
可以。它的存在意义就是不让外界创建当前这个类的对象。这样的类符合的设计模式中的 适配器模式
。
5、抽象关键字不能和哪些关键字共存?
private :父类中私有的成员变量和方法子类是不知道的,因此使用private 和abstract关键字一起修饰方法,导致子类根本无法知道父类中有这个抽象方法,从而子类无法实现重写。
static:如果使用static和abstract关键字一起修饰抽象方法,导致这个方法使用类名直接调用,从而无需创建对象, 而抽象方法是没有具体的方法实现体的.因此调用抽象方法是没有任何意义的。
final :final修饰的方法子类是无法重写的,而abstract修饰的抽象方法,目的就是为了要求子类进行重写。
抽象类何时使用:
当描述事物体系,一般在描述所有体系中最共性的内容时,通常是只知道体系的共性功能,却无法书写具体功能体,这时会使用抽象方法表示,那么这个类一定要使用抽象类表示。
抽象类接口(day03)
接口:
说明 : 抽象类中可以有抽象方法, 但同时也可以包含非抽象方法.
注意 : 接口中定义的方法和变量都包含一些默认修饰符.接口中定义的方法默认使用 “public abstract” 来修饰,即抽象方法.接口中变量默认使用 “public static final” 来修饰 .使用类名直接访问. 接口中的变量,都是常量。因此接口中的变量名一般都会全部大写。默认修饰符可以省略不写.
由于接口中的方法都是抽象方法,因此不能通过实例化对象的方式来调用接口中的方法.此时需要定义一个类,并使用 implements 关键字实现接口中所有的方法.
注意 : 一个类在继承另一个类的同时还可以实现接口,此时 extends 关键字必须位于 implements 关键字之前.
接口与多态 : 接口类型可以接收任何实现了该接口的实现类对象. (面向接口调用 / 面向接口编程)
总结:
类和类:继承关系,一个类只能继承一个父类。 单继承
类和接口:实现关系,一个类可以实现多个接口。 多实现
接口和接口:继承关系,一个接口可以继承多个接口。 多继承
类和类之间是继承,存在子类和父类的关系。子类需要覆盖父类的方法时,我们称为方法的重写。
类和接口之间是实现关系,实现接口的类,一般称为实现类,实现类中对接口中的方法进行重写的书写。这时称为实现类对接口的方法进行的实现.
Animal a=new Cat();
a.setName("波斯猫");
a.shout();
// 解决方案一 : 将 a 对象强转为 Guidable 接口
// 多态的第一种行为 : 接口类型可以接收任何实现了该接口的实现类对象.
// 强转不分对象. 强转的目的就是为了告诉编译器该对象是什么类型.
Guidable g=(Guidable) a;
g.blindGuiding();
// 解决方案二 : 将 a 对象强转为 Cat 类
Cat c=(Cat) a;
c.blindGuiding();
public class Computer {
public void UseUsb(Usb u){
//多态:接口引用可以接收任何实现该接口的类的对象
// Usb u=new Mouse();
if(u!=null){
u.run();
u.close();
}
}
}
public class Test {
public static void main(String[] args) {
Mouse m=new Mouse();
Computer c=new Computer();
c.UseUsb(m);
//使用方法内部类完成 完成类似的功能
class Keyboard implements Usb{
@Override
public void run() {
System.out.println("keyboard run");
}
@Override
public void close() {
System.out.println("keyboard close");
}
}
c.UseUsb(new Usb() {
@Override
public void run() {
System.out.println("匿名内部类的run方法.....");
}
@Override
public void close() {
System.out.println("匿名内部类的close方法.....");
}
});
c.UseUsb(new Keyboard());
}
}
抽象类和接口的区别 :
接口和抽象类都是描述事物的行为并且也可以定义常量和成员属性用于存储数据,并且描述的行为一般都是抽象的。需要子类或实现类对这些行为进行实现或重写。
1. 抽象类和接口都可以有 abstract 方法.
2. 接口中可以有常量,不能有变量,而抽象类中既可以有常量也可以有变量.
3. 抽象类中可以有非 abstract 方法.接口不可以.
4. 抽象类是继承体系的一员, 拥有类创建对象的构造方法, 但是接口中不存在 `构造方法`.
总结 : 抽象类依然是属于类继承体系中的一员。而接口则是类继承体系之外的事物描述.也可称为事物的扩展功能。
抽象类 : 表示一类事物的共性属性和行为.
接口 : 对事物的行为进行功能的扩展.
当然,如果抽象类的父类仅仅只有一两个抽象方法的话,我们也同样可以使用匿名内部类来实现.但是注意,无论内部类还是匿名内部类,方法不要过多,否则阅读性会很差.
内部类(day04)
匿名内部类 :
1. 接口的实现类.
2. 抽象类的子类对象.
public static void main(String[] args) {
// 1. 创建一个 `Computer` 类的对象
Computer com = new Computer();
com.run();
com.useUSB(new Mouse());
}
//匿名内部类 通过实现接口的方式实现 基础是多态
/*
匿名内部类的格式 :
new 父类() 或 接口() {
// 匿名内部类的实现代码
}
*/
// 说明1: new USB() 表示创建一个`接口`类型的对象. 接口不能创建对象,接口只能够被实现.
// 说明2: new USB() {...} 表示实现接口.它就是一个接口的实现类. ... 表示实现接口中的代码
// 请问: 这个接口的实现类有名称吗? 没有名称. 因此这种方式就称为 `匿名内部类`
// 1. 创建一个 `Computer` 类的对象
Computer com = new Computer();
com.UseUsb(
new Usb(){
@Override
public void close() {
System.out.println("close...");
}
@Override
public void open() {
System.out.println("open...");
}
}
);
//说明 :
//1.调用c.useUSB() 方法时,在方法的参数位置上写 new USB() { } 这就相当于创建了一个USB接口的一个实现类对象. new USB() 后面有一对大括号,表示创建的对象为USB的实现类对象.该实现类是匿名的.
//2.在大括号中编写匿名实现类的实现代码就可以了.
public static void main(String[] args) {
// 1. 创建一个 `Computer` 类的对象
Computer com = new Computer();
com.run();
com.useUSB(new Mouse());
// 在main方法中创建一个类 (方法内部类)
class Keyboard implements USB {
// 属性
// 行为
public void open() {
System.out.println("Keyboard open...");
}
public void close() {
System.out.println("Keyboard close...");
}
}
com.useUSB(new Keyboard());
}
匿名内部类的局限性 : 由于匿名内部类没有类名称.因此在多态语句中无法向下转型.
Object类介绍
在Object类中的equals方法内容使用的 关系运算的 == 在比较2个对象是否相同。
this 表示的是调用这个equals方法的那个对象,obj是调用equals方法时传递进来的那个对象,
而this中保存的是对象的内存地址,obj中接收的也是传递进来的那个对象内存地址。而使用 == 其实是在比较2个对象的内存地址是否相等。
public boolean equals(Object obj) {
return (this == obj);
}
测试 : 定义了一个Person类,然后直接创建了2个Person对象,如果Person类两个对象的的姓名和年龄都相同,我们就认为是同一个人,调用equals应该返回true.
public boolean equals(Object obj){
if(obj==this)//如果地址都相等了 那么内容一定是相等的
return true;
if(obj instanceof Object){//如果地址不相等 那么判断类型 并且取出里面的内容进行比较 如果相等则相等
Person p=(Person)obj;
if(p.name==this.name&&p.age==this.age){
return true;
}
}
return false;
}
查看String类的源代码,看看equals是怎么书写的。
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
//String s1=new String("abc");
//String s2=new String("abc");
String s1="abc";
String s2="abc";
System.out.println(s1.equals(s2));//比较的是内容 如果相等 则相等 也就是说字符串里面的equals重写了Object的equals方法
查看Object类的源代码,看看toString是怎么书写的。
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
toString() 方法详解 :
1. getClass().getName() 代表返回对象所属类的类名.即cn.itcast.Person.
2. hashCode() 代表返回该对象的哈希值. 这个方法将对象的内存地址进行哈希运算.返回一个int类型的哈希值.
3. Integer.toHexString(hashCode)) 代表将对象的哈希值用16进制来表示.
在我们直接打印一个对象的引用变量时,这个时候,会自动调用这个对象的toString方法。
Object类中的toString方法是把当前的对象信息打印出来。但是这个打印的结果是 完整类名@内存地址
记住:开发中,如果要写一个类,一般都需要重写Object类中的equals和toString方法。
equals : 自定义对象的 判断标准
.
toString() : 自定义对象的输出 数据信息
异常(day05)
异常的使用细节
1、如果在方法中使用throw关键字抛出了异常,在throw语句的下面不能直接编写其他的代码,否则编译报错。
2、继承中的方法重写异常细节: 子类重写父类的方法时,如果父类的方法上没有声明编译时异常.
1. 子类重写的时候,也不能声明编译时异常, 但子类可以抛出运行时异常.
2. 如果子类重写父类方法,并抛出编译时异常,程序错误.
3、接口的实现类实现接口的抽象方法细节:如果接口中的抽象方法没有声明编译时异常, 实现类实现该方法时同样不能声明 编译时异常
. 但可以抛出运行时异常.
静态和非静态代码案例
public class Student {
//属性
// 思考 : 如果该数据每个对象都不一样, 那么该属性必须为 `非静态属性`. 非静态在堆内存中每个对象都有自己的一份.
private String name;
private int age;
private char gender;
// 定义一个 PI
// 思考 : 静态属性属于该类的. 直接用类名访问, 与对象无关, 不会在堆区中开辟空间, 在内存中仅有一份. (节省内存空间)
public static final float PI=3.14f;
//行为
// introduce 方法中使用到了 `成员属性`, 成员属性是属于 `对象`
// 小结 : 如果一个方法中使用到了成员属性, 那么该方法就必须定义为 `非静态 / 对象` 方法
public void introduce() {
// this -> stu, stu2
System.out.println(this.PI);//非静态方法可以访问静态成员
System.out.println("大家好, 我叫" + name + ", 我今年" + age + "岁了. " + gender);
}
// 需求1 : 求长方形的面积 公式: 宽 * 高
// 该方法没有使用任何 `成员属性`, 那么该方法就应该被定义为 `静态方法`
public static int getAreaOfRect(int width, int height) {
//System.out.println(this.age);//静态方法无法访问非静态成员
return width * height;
}
// 需求2 : 求圆的面积 公式: π * r * r -> Radius 半径
public static double getAreaOfCircle(double radius) {
return PI * radius*radius;
}
}
系统类 正则类 包装类(day06)
访问权限修饰符 : private < 默认 < protected < public
default:如果一个类或者类的成员不使用任何访问控制修饰符 那么它为默认访问控制级别 这个类或者类的成员只能被本包中的其他类访问
protected:如果一个类的成员被protected访问控制符修饰 那么这个成员既能被同一包下的其它类访问 也能被不同包下的该类的子类访问
Random类,它可以在指定的取值范围内随机产生数字.
每次运行产生的随机数序列是不一样的,这是因为当创建Random的实例对象时,没有指定种子,系统会以当前时间戳作为种子,产生随机数.
当创建Random类的实例对象时,如果指定了相同的种子,则每个实例对象产生的随机数具有相同的序列.
Random类提供了更多的方法来生成各种伪随机数,不仅可以生成整数类型的随机数,还可以生成浮点类型的随机数.
其中Random类的nextDouble()方法返回的是0.0和1.0之间double类型的值.nextFloat()方法返回的是0.0和1.0之间float类型的值.nextInt(int n)返回的是0(包括)和指定值n(不包括)之间的值.
补充String类的jdk源码说明文件
/**
* The {@code String} class represents character strings. All
* string literals in Java programs, such as {@code "abc"}, are
* implemented as instances of this class.
* <p>
* Strings are constant; their values cannot be changed after they
* are created. String buffers support mutable strings.
* Because String objects are immutable they can be shared. For example:
* <blockquote><pre>
* String str = "abc";
* </pre></blockquote><p>
* is equivalent to:
* <blockquote><pre>
* char data[] = {'a', 'b', 'c'};
* String str = new String(data);
* </pre></blockquote><p>
* Here are some more examples of how strings can be used:
* <blockquote><pre>
* System.out.println("abc");
* String cde = "cde";
* System.out.println("abc" + cde);
* String c = "abc".substring(2,3);
* String d = cde.substring(1, 2);
// 需求 : 将字符串中的字符按照字典顺序排序,并获取排序后的字符串
String str="helloworld";
// 将字符串转成字符串数组
char[] charArr = str.toCharArray();
System.out.println(charArr);//char[] 数组直接输出类似于字符串输出
Date日期类
public static void main(String[] args) {
// 1. new Date();
Date date=new Date();
System.out.println(date);
// 2. getTime()
long time=date.getTime();
System.out.println("time:"+time);
Long time2 = System.currentTimeMillis();
System.out.println("currentTimeMillis:"+time2);
// 3. 设置距离标准时间一小时之后的日期
Date date2 = new Date(1000 * 60 * 60);
System.out.println(date2);
// 4. setTime() 设置时间
date.setTime(1000 * 60 * 60);
System.out.println(date);
}
private static void method_02() throws ParseException {
// 1. 创建一个 `SimpleDateFormat` 格式化器
DateFormat df=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
// 2. 解析
// 准备参数
String str = "2011-11-11 11:11:11";
Date date = df.parse(str);
// 3. 打印
System.out.println(date);
}
// 1. 默认格式化
public static void method_01() {
DateFormat df=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date=new Date();
String date_Str=df.format(date);
System.out.println(date_Str); //2018-07-10 16:52:07
}
public static void main(String[] args) {
// Calendar 日期类的演示
Calendar c=Calendar.getInstance();
// 3. 需求 : 我想回到 2008 年的今天
c.set(Calendar.YEAR, 2008);
// 4. 需求: 我想回到 2008 年的 8 月
c.set(Calendar.MONTH, 7);
// 5. 需求: 我想回到 2008 年的 8 月 8 号
c.set(Calendar.DAY_OF_MONTH, 8);
printCalendar(c);//今天是2008年7月8日
// 同时设置 `年月日`
c.set(2011, 10, 11);
printCalendar(c);//今天是2011年10月11日
// 6. 需求: 10 年之后
c.add(Calendar.YEAR, 10);
printCalendar(c);//今天是2021年10月11日
// 7. 需求: 5个月之前
c.add(Calendar.MONTH, -5);
printCalendar(c);//今天是2021年5月11日
}
private static void printCalendar(Calendar c) {
int year=c.get(Calendar.YEAR);
int month=c.get(Calendar.MONTH);
int day=c.get(Calendar.DAY_OF_MONTH);
System.out.println("今天是"+year+"年"+month+"月"+day+"日");
}
正则案例
// 字母和数字组合 : 以数字作为分隔符进行切割
String str="boxing123basketball4567football888ILOVEYOU9876爱我中华";
String[] splitStrArr=str.split("\\d+");
for(int i=0;i<splitStrArr.length;i++){
System.out.println(splitStrArr[i]);
}
String str = "boxing#######basketball#####football###ILOVEYOU###爱我中华";
String regEx = "#+";
// 切割 调用String对象的split方法
String[] stringArray = str.split(regEx);
// 遍历,查看字符数组中的元素内容
for (int i = 0; i < stringArray.length; i++) {
System.out.println(stringArray[i]);
}
// 把正则分成三组,第二组使用****替换
String regExp="(1[1475]\\d)(\\d{4})(\\d{4})";
// 替换 : 使用 String对象的replaceAll方法
// 在参数列表中,其它参数要使用之前参数中规则的组,需要使用$组编号
String regExpResult=tel.replaceAll(regExp,"$1****$3" );
System.out.println(regExpResult);
新特性 Collection iterator(day07)
基本数据类型 & 包装类 & 字符串 相互转换
public static void main(String[] args) {
// 1. 基本数据类型转包装类型
Integer integer_num1=new Integer(100);
Integer integer_num2=Integer.valueOf(200);
// 2. 包装类型转成基本数据类型
int int_num3=integer_num1.intValue();
// 3. 基本数据类型转字符串
String num1_str = int_num3 + "";
String str_num4=String.valueOf(int_num3);
String str_num6=Integer.toString(100);
// 4. 字符串转基本数据类型
int int_num5=Integer.parseInt(str_num4);
// 5. 包装类型转字符串
String num4_str=integer_num2.toString();
// 6. 字符串转包装类型
Integer integer_num3= new Integer("100");
Integer integer_num4=Integer.valueOf("99");
}
可变参数
public static int getSum(int... array) {
System.out.println("222");
int sum = 0;
for (int i = 0; i < array.length; i++) {
sum += array[i];
}
return sum;
}
Collection
集合与数组的区别
1. 从长度来看 :
数组 : 长度固定.
集合 : 长度可变. 集合底层就是 可变数组
2. 从储存类型看 :
数组 : 可以存储 基本类型
和 引用类型
. int[] float[] String[] Student[]
集合 : 只能存引用类型, 不能存储基本数据类型.
public class Student {
//属性
private String name;
private Integer age;
//构造方法
public Student(String name, Integer age) {
this.name = name;
this.age = age;
}
// 铁律 : 只要往哈希表中存储元素, 就必须重写自定义对象的 `hashCode + equals` 方法.
// 哈希表通过自定义对象的 `hashCode + equals` 方法共同保证元素的唯一性.
// 重写 hashCode 方法
// 根据数据内容来计算哈希值
@Override
public String toString() {
return "Student [name=" + name + ", age=" + age + "]";
}
@Override
public boolean equals(Object obj) {
if(obj==this)
return true;
if(obj instanceof Student==false){
throw new ClassCastException("类型转换异常");
}
Student stu=(Student) obj;
if(this.name.equals(stu.getName())&&this.age==stu.getAge()){
return true;
}
return false;
}
//getter&&setter
}
// 需求1 : keySet + iterator
Set<String> set=map.keySet();
Iterator<String> it=set.iterator();
while(it.hasNext()){
String country=it.next();
String capital=map.get(country);
System.out.println(country+"首都是:"+capital);
}
System.out.println("****************************************************");
// 需求2 : entrySet + foreach
Set<Entry<String, String>> set2=map.entrySet();
for(Entry<String,String> en:set2){
String country=en.getKey();
String capital=en.getValue();
System.out.println(country+"首都是:"+capital);
}
System.out.println("****************************************************");
// 需求3 : keySet + foreach
Set<String> set3=map.keySet();
for(String country:set3){
String capital=map.get(country);
System.out.println(country+"首都是:"+capital);
}
System.out.println("****************************************************");
// 需求4 : entrySet + iterator
Set<Entry<String, String>> set4=map.entrySet();
Iterator<Entry<String,String>> it4=set4.iterator();
while(it4.hasNext()){
Entry<String,String> entry=it4.next();
String country=entry.getKey();
String capital=entry.getValue();
System.out.println(country+"首都是:"+capital);
}
public static void main(String[] args) {
// Collections 中常用方法演练 :
ArrayList<Integer> list = new ArrayList<Integer>();
list.add(8);
list.add(18);
list.add(66);
// 1. sort
Collections.sort(list);
System.out.println(list);
// 2. binarySearch 二分查找 / 折半查找 / 二进制查找 比普通查找效率高.
// 前提条件 : 集合必须有序
int index = Collections.binarySearch(list, 66);
System.out.println(index);
// 3. max
Integer max = Collections.max(list);
System.out.println(max);
// 4. min
Integer min = Collections.min(list);
System.out.println(min);
// 5. reverse
Collections.reverse(list);
System.out.println(list);
// 6. shuffle
Collections.shuffle(list);
System.out.println(list);
}
public static void main(String[] args) {
//区别那些方法是Collection和List定义的
// ArrayList 实现了 List 接口. List 接口继承 Collection 接口
// 为什么 ???
// 多态 : 接口引用可以接收任何该接口的实现类对象
// 特点 : 使用 c 对象调用 Collection 接口中定义的方法.
// ArrayList 自己特有的方法不可以调用.
// toString() 方法
Collection<String> c = new ArrayList<String>();
c.add("hello");
c.add("world");
c.add("welcome");
System.out.println(c.toString()); // [hello, world, welcome] 运行
// Collection 接口定义: add, size
// List 接口定义: get, set, remove,
}
public static void main(String[] args) {
//add, contains, equals, hashCode, isEmpty, remove, clear, size, toArray.
// Collection 接口中常用方法的介绍和使用 :
Collection<String> c=new ArrayList<String>();
// add 添加对象
c.add("hello");
c.add("world");
// contains 判断是否包含
boolean result = c.contains("hello");
System.out.println(result);//true
// equals 判断集合是否相等
String[] arr={"hello","world"};
boolean result2=c.equals(arr);
System.out.println(result2);//false
Collection<String> c2=new ArrayList<String>();
c2.add("hello");
c2.add("world");
boolean result3=c.equals(c2);
System.out.println(result3);//true
// hashCode 作用: 获取元素在集合 `可变数组` 中的存储位置. HashSet 具体讲解
// 哈希值 : 如果两个集合中元素内容完全相同, 那么集合计算出的哈希值就 `应该` 相同. 哈希值应该根据元素内容来实现计算.
int hash1=c.hashCode();
int hash2=c2.hashCode();
System.out.println(hash1==hash2);//true
// isEmpty 判断集合是否为空
boolean result4=c2.isEmpty();
System.out.println(result4);//false
// remove 移除集合中的元素
boolean result5=c2.remove("good");//false;
// clear 清空集合中的所有元素
//c2.clear();
// size 获取集合中元素的个数
System.out.println(c2.size());
// toArray 将集合转换成数组
Object[] obj=c2.toArray();
for(Object o:obj){
System.out.println(o);
}
System.out.println("------------------");
for(int i=0;i<obj.length;i++){
System.out.println(obj[i]);
}
}
//有序, 可重复 和 无序, 不可重复 遍历方式一样吗? 肯定不一样.
// Collection 父接口 List 和 Set 都是子接口. 请问 : 父类如何给两个不同的子接口定义相同的元素遍历规范吗 ???
// 解决方案 : 父接口给两个子接口定义了一个 `iterator 迭代` 方案. 不一样如何实现呢 ??? 实现类自己去实现.
// Collection<String> c = new HashSet<String>();
//并发修改异常. 迭代的时候只能使用迭代器的方法, 不可以使用集合的方法.
public class IteratorDetailsDemo {
public static void main(String[] args) {
Collection<String> c = new ArrayList<String>();
c.add("柳岩");
c.add("刘亦菲");
c.add("范冰冰");
c.add("李冰冰");
c.add("柳岩");
c.add("凤姐");
c.add("汤唯");
c.add("凤姐");
for(String s:c){
if(s.equals("凤姐")){
// 删除 : 这里没有解决方案, 除非使用 `Iterator / 迭代器`.
//c.remove(s);
}
}
// 迭代, 并删除 `凤姐`
// 1. hasNext 判断一下是否有下一个元素
Iterator<String> it=c.iterator();
while(it.hasNext()){
String name=it.next();
if("凤姐".equals(name)){
// 如果条件成立, 删除
// 正确的 : 一定要使用迭代器删除
it.remove();
// 错误的 :
// Concurrent 并发 Modification 修改 Exception 异常 并发 : 多个对象在操作集合
// c.remove(name);
}
}
System.out.println(c.toString());
}
}
注意点 :
使用迭代器对集合进行迭代的时候,不要使用集合自身的功能对集合进行增删改操作, 要使用迭代器的方法进行相对应的操作.
泛型(day08)
说明 : 集合中的保存数据的时候,数据保存到集合中之后,都会被提升成Object类型。
我们存储的时候,任何类型都让我们存储.然而,我们在取的时候,就可能会发生报错,并抛出了类型转换异常,非常不靠谱!
你应该在存的时候就告诉我们,这个集合中只能存储字符串,如此的话,我们在取时进行类型转换就不会发生异常.
总结 : 泛型将运行时可能会发生的异常提前到了编译时期进行的语法检查,将隐患问题提前进行了处理.
自定义泛型类
泛型方法
// 定义一个 泛型
方法, 注意: 必须要在方法之前声明 该泛型
,哪怕该方法其实没有方法返回值 否则不能在参数列表中使用
private static<T> T printInfo(T t) {
System.out.println(t);
}
public static void main(String[] args) {
/*
泛型的小结 : 泛型就是参数化类型.
*
* 1. 泛型可以将运行阶段可能发生的 `类型转换异常` 提前到编译阶段进行检查. 提升程序的 `健壮性 / 稳定性`.
* 2. 泛型解决了类型强转的问题. (从集合中取出的对象类型无序转换)
* 3. 泛型是 `编译时期` 的技术, 在程序运行阶段, 不存在泛型. 泛型仅仅就是给代码作 `编译检查` 的. 泛型能够明确一个集合容器中的同一类型定义.
*
*/
// parameter 形式参数 argument 实际参数 variable arguments 可变参数
// ArrayList is a raw type. References to generic type ArrayList<E> should be parameterized
// ArrayList 是一个原始类型, list 引用指向了一个泛型类, 该类上的泛型需要被 `参数化`.
// 注意1 : 如果泛型不指定参数化类型, 此时, 泛型就会自动提升为 Object 类型.
// ArrayList list = new ArrayList();
ArrayList<String> list = new ArrayList<String>();
list.add("hello");
list.add("world");
// list.add(998);
// list.add(true);
// list.add('A');
Iterator<String> it=list.iterator();
while(it.hasNext()){
// Object obj = it.next();
// System.out.println(obj);
// 需求: 迭代的同时, 查看字符串的长度
// String str = (String) it.next();
String str=it.next();
System.out.println(str+"----"+str.length());
}
}
List 的特点 :
1. 存取有序.
2. 元素有索引,可以像数组一样操作.
3. 元素可以重复.
ListIterator接口:它是专门针对List接口而设计的, 它可以实现逆向遍历。并在迭代的同时对元素进行增删改查
的操作.
public static void main(String[] args) {
// List 特有迭代器演示 :
List<Student> list = new ArrayList<Student>();
list.add(new Student("柳岩", 18));
list.add(new Student("范冰冰", 20));
list.add(new Student("汤唯", 16));
//需求 : 存储自定义Student对象. 柳岩, 范冰冰, 汤唯
ListIterator<Student> listIt = list.listIterator();
while (listIt.hasNext()) {
Student stu = listIt.next();
System.out.println(stu);
}
// ListIterator 接口, 实现正向迭代的同时, 并设置集合中的元素
// ListIterator 在迭代的同时可以对元素进行 `增删改查`.
// 需求 : 在范冰冰之后添加 `刘亦菲`, 删除 `汤唯`, 并将 `柳岩` 设置为 `赵薇`
ListIterator<Student> listIt2=list.listIterator();
while(listIt2.hasNext()){
Student stu=listIt2.next();
if(stu.getName().equals("范冰冰")){
listIt2.add(new Student("刘亦菲",18));
} else if (stu.getName().equals("汤唯")) {
listIt2.remove();// 一定要使用器的方法
} else if (stu.getName().equals("柳岩")) {
listIt2.set(new Student("赵薇", 30));
}
}
System.out.println(list);
System.out.println("--------------------------------------------------------------------------");
// ListIterator 接口, 实现元素的逆向迭代.
// list.listIterator(); 迭代器的初始光标在第 0 个元素之前.
// ListIterator<E> listIterator(int index) 逆向迭代需要设置光标的初始位置 `集合的长度`
ListIterator<Student> list2=list.listIterator(list.size());
while(list2.hasPrevious()){
Student stu=list2.previous();
System.out.println(stu);
}
}
关于list中get方法的使用细节 – 删除指定元素
for(int i=0;i<list.size();i++){
Student student = list.get(i);
if(student.getName().contains("冰冰")){
list.remove(student);
i--;
}
}
使用 List 存储自定义对象,并去除重复元素
public static void main(String[] args) {
/*
* List 接口:
* 1. 有下标. 可以存储重复元素
*/
// 练习 : 使用 List 存储自定义对象,并去除重复元素
// 需求 : 存储自定义Student对象. 柳岩, 范冰冰, 汤唯
List<Student> list = new ArrayList<Student>();
list.add(new Student("柳岩", 18));
list.add(new Student("范冰冰", 20));
list.add(new Student("汤唯", 16));
list.add(new Student("柳岩", 18));
list.add(new Student("范冰冰", 20));
List<Student> list2 = new ArrayList<Student>();
for(Student s:list){
if(list2.contains(s)==false){//这里必须重写equals方法 否则不能判断相当
list2.add(s);
}
}
list.clear();
list.addAll(list2);
System.out.println(list);
System.out.println(list2);
}
contains方法的底层最终是依赖于equals方法
元素类型为String类型时,过滤重复元素是成功的,因为String类重写了equals方法 比较的是内容
Student类 没有重写equals方法 比较的是内存地址 因此认为每一个学生都不同
解决方法:重写Student的equals方法比较学生的姓名和年龄
@Override
public boolean equals(Object obj) {
if(obj==this)
return true;
if(obj instanceof Student==false){
throw new ClassCastException("类型转换异常");
}
Student stu=(Student) obj;
if(this.name.equals(stu.getName())&&this.age==stu.getAge()){
return true;
}
return false;
}
LinkedList集合它采用的是数据结构中的链表结构:
ArrayList集合它采用的是数据结构中的线性表 (数组) 结构.
说明 : LinkedList 结构是一个 双链表
数据结构, 有头有尾, 因此 LinkedList 集合中定义了自己的特有方法都是围绕链表的头和尾设计的.
Set接口与HashSet
哈希表:哈希表保证对象唯一需要依赖对象的hashCode和equals方法。
Set接口的特点:
1.不允许重复元素
2.存取无序
3.没有索引
HashSet接口的特点:
1.不允许重复元素
2.存取无序
3.没有索引
4.底层是hash表
如果对象的 姓名
和 年龄
一致, 代表对象重复,不存储
// 铁律 : 只要往哈希表中存储元素, 就必须重写自定义对象的 hashCode + equals
方法.
// 哈希表通过自定义对象的 hashCode + equals
方法共同保证元素的唯一性.
// 重写 hashCode 方法
// 根据数据内容来计算哈希值
@Override
public int hashCode() {
// String name 字符串已经重写了 hashCode 方法. + age
return name.hashCode() + age;
}
@Override
public boolean equals(Object obj) {
if(obj==this)
return true;
if(obj instanceof Student==false){
throw new ClassCastException("类型转换异常");
}
Student stu=(Student) obj;
if(this.name.equals(stu.getName())&&this.age==stu.getAge()){
return true;
}
return false;
}
LinkedHashSet -> 链表 + 哈希表 共同实现的.
链表 -> 存取有序.
哈希表 -> 不重复
LinkedHashSet集合:
它的底层使用的 链接表+哈希表
结构。 它和HashSet集合的区别是LinkedHashSet是一个可以保证存取顺序的集合,并且LinkedHashSet集合中的元素也不能重复。
1.”Collection 结构总结 :”
2.
3.|– List
4. “特点 :” 存取有序,元素有索引,元素可以重复.
5. |– ArrayList : 数组结构,查询快,增删慢,线程不安全,因此效率高.
6. |– Vector : 数组结构,查询快,增删慢,线程安全,因此效率低.
7. |– LinkedList : 链表结构,查询慢,增删快,线程不安全,因此效率高.
8. addFirst() removeFirst() getFirst()
9.
10.|– Set
11. “特点 :” 存取无序,元素无索引,元素不可以重复.
12.
13. |– HashSet : 存储无序,元素无索引,元素不可以重复.底层是哈希表.
14. 请问 : 哈希表如何保证元素唯一呢 ? 底层是依赖 hashCode 和 equals 方法.
15. 当存储元素的时候,先根据 hashCode + 数组长度 计算出一个索引,判断索引位置是否有元素.
16. 如果没有元素,直接存储,如果有元素,再判断 equals 方法,比较两个元素是否相同,不同则存储,相同则舍弃.
17. 我们自定义对象存储的元素一定要实现 hashCode 和 equals.
18.
19. |– LinkedHashSet : 存储有序,元素不可以重复.
20.
21.”总结 : 如何选择用哪个集合呢?”
22.
23.”问题1 : 元素是否可以重复呢?”
24. 可以 : List 系列.
25. “问题2 : 查询多还是增删多?”
26. 查询多 : ArrayList
27. 增删多 : LinkedList
28. 不可以 : Set 集合.
Map接口:
Map 和 Collection 的区别 :
1. Map是双列集合. Collection是单列集合.
2. Map中键唯一,值没有要求. Collection中只有 Set 体系要求元素唯一.
3. Map的数据结构针对键而言. Collection的数据结构针对元素而言.
备注 :
1. HashMap 的 key 是通过 `哈希表` 来保证唯一性的.
2. 哈希表是通过 `hashCode + equals` 方法共同来保证唯一性的.
说明 :
Map集合中保存的一组对象(一对),不管是key还是value都是对象。我们现在可以自己定义一个对象作为HashMap集合的key值。HashMap集合的key是需要使用哈希表来保证唯一。因此自定义对象就需要重写Object类中的hashCode和equals方法。
1.”Map总结 :”
2.
3.|–HashMap
4. |– LinkedHashMap
5.请问. 是否需要存取有序呢?
6. 是 : LinkedHashMap
7.
8.”面试题 : HashMap 和 Hashtable 的区别 ?”
9. 联系 :
10. 底层都是哈希表结构,所有键唯一,存取无序.
11. 区别 :
12. 1. HashMap 可以允许 null. Hashtable 不允许 null.
13. 2. HashMap 线程不安全,所以效率高, Hashtable线程安全,所有效率低.
14.
15.哈希表
: hashCode & equals
Collection集合工具类
public static void main(String[] args) {
// 需求 : 使用 ArrayList 集合存储自定义对象, 并排序
ArrayList<Student> list = new ArrayList<Student>();
list.add(new Student("刘德华", 18));
list.add(new Student("张学友", 22));
list.add(new Student("黎明", 17));
list.add(new Student("郭富城", 20));
// 问题 : Integer 和 Student 到底有啥区别 ???
// Bound mismatch: 绑定不匹配
// sort(List<T>) 方法中的参数 List 接口需要一个 T 泛型.
// bounded parameter <T extends Comparable> 绑定的参数 T 必须要继承 Comparable 接口.
Collections.sort(list);
System.out.println(list);
}
public class Student implements Comparable<Student>{
private String name;
private Integer age;
//省略部分
}
@Override
public int compareTo(Student o) {
Integer result=this.age-o.age;
if(result==0){
result = this.name.compareTo(o.name);
}
return result;
}
public static void main(String[] args) {
/*
* 结果 :
* 0 表示两个对象相等.
* 正数 调用对象大于传入参数对象
* 负数 调用对象小于传入参数对象
*/
// 一个字符一个字符比, 一旦有结果, 立刻返回. 后面就不比了.
String s1 = "abcd"; // 97
String s2 = "ABCD"; // 65
int result=s1.compareTo(s2);
System.out.println(result);
}
public static void main(String[] args) {
// 需求 : 使用 ArrayList 集合存储自定义对象, 并排序
ArrayList<Student> list = new ArrayList<Student>();
list.add(new Student("刘德华", 18));
list.add(new Student("张学友", 22));
list.add(new Student("黎明", 17));
list.add(new Student("郭富城", 20));
// c 的类型为 Comparator 接口类型, 所以 c 对象应该是 Comparator 接口的实现类
// 说明 : 如果两个` 比较 (自然排序 / 比较器排序) ` 都实现, 以 `比较器排序` 作为优先.
// Collections.sort(list, new MyComparator());
// 需求 : 比较器只在当前代码实现, 其它类不允许使用.
// 匿名实现类 : new Comparator(){}; 这就是 Comparator 接口的匿名实现类对象
Collections.sort(list, new MyComparator());
System.out.println(list);
}
//自定义一个 Comparator 接口的实现类
//外部类的特点 : 其它类都可以使用.
class MyComparator implements Comparator<Student>{
@Override
public int compare(Student o1, Student o2) {
Integer result=o1.getAge()-o2.getAge();
if(result==0){
result=o1.getName().compareTo(o2.getName());
}
return result;
}
}
File类
/*
file.listFile() 里面可以接收FilenameFilter和FileFilter接口的实现类
file.list() 里面只能接收FilenameFilter的接口的实现类
*/
public static void main(String[] args) {
// 练习 (判断.jpg后缀名的文件) 打印文件的 `绝对路径`
File file = new File("C:\\Users\\User\\Desktop\\JavaSE部分\\Day11_字节流\\课程资料");
// filter 过滤器 -> FilenameFilter 文件名称过滤器接口
// 需求1 : jpg 文件
String[] list = file.list(new MyFilenameFilter(".jpg"));
for(String f:list){
System.out.println(f);
}
}
//自定义一个文件名称过滤器, 到底过滤什么后缀名的文件, 应该由外部传入
class MyFilenameFilter implements FilenameFilter{
private String suffix;
public MyFilenameFilter(String suffix) {
this.suffix=suffix;
}
// 不应该提供一个无参的构造方法
@Override
public boolean accept(File dir, String name) {
// System.out.println("accept ... ");
// System.out.println(dir + " : " + name);
// 返回 false, 意味着当前 `列表` 的文件不会被转入 String[] names 数组中.
// 返回 true, 意味着当前 `列表` 的文件会被转入 String[] names 数组中.
// 需求: jpg 转入, 不是的话, 不装.
File filename = new File(dir,name);
if(name.endsWith(suffix)&&filename.isFile())
return true;
return false;
}
}
//自定义一个 FileFilter 实现类
class MyFileFilter implements FileFilter{
private String suffix;
// 请问 : 如何保障 suffix 属性一定有值.
// 通过构造方法来保证, 并且不能提供无参构造方法
public MyFileFilter(String suffix) {
this.suffix=suffix;
}
@Override
public boolean accept(File pathname) {
if(pathname.getName().endsWith(suffix)&&pathname.isFile()){
return true;
}
return false;
}
public static void main(String[] args) {
File file = new File("C:\\Users\\User\\Desktop\\JavaSE部分\\Day11_字节流\\课程资料");
File[] listFiles = file.listFiles(new MyFileFilter(".java"));
System.out.println(listFiles);
for(File f:listFiles){
System.out.println(f);
System.out.println(f.getName());
}
}
流
字符流 = 字节流 + 编码表
编码表其实里面定义了字节与字符的转换规则.
字符流在读取数据的时候,底层还是字节流在读取数据,字符流会自动根据编码规则把字节数据转为字符数据,就不会造成错误!
说明 : 便捷类和父类之间的关系
1.”便捷类 和 父类 之间的关系 :”
2.”父类 :”
3.1. OutputStreamWriter = OutputStream + 任意编码表
4.2. InputStreamReader = InputStream + 任意编码表
5.”子类 :”
6.1. FileWriter = FileOutputStream + 默认编码表
7.2. FileReader = FileInputStream + 默认编码表
8.
9.”总结 : 一般我们都使用子类,便捷类,当需要指定编码表的时候,才会使用父类 (转换流)”
public static void main(String[] args) throws IOException {
// 字符转换输出流 :
// 需求 : 使用 UTF-8 字符编码写入数据
// 1. 创建一个 `转换流` 对象 字符 -> 字节 + 编码表
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("demo1.txt"), "UTF-8");
// 选择一 : GBK : 转换流 + gbk 编码
// 选择二 : 便捷流 FileWriter
// FileWriter -> OutputStreamWriter + 默认编码表
// OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream("demo3_gbk.txt"), "GBK");
osw.write("How old are you");
osw.write("怎么老是你");
osw.flush();
osw.close();
}
public static void main(String[] args) throws IOException {
// 字符转换输入流 :
// 需求 : 读 UTF-8 写入的数据内容
// 1. 创建一个 `字符转换输入流` UTF-8 utf-8 UTF8 utf8 UtF8
InputStreamReader isr = new InputStreamReader(new FileInputStream("demo1.txt"), "UTF-8");
int content=-1;
while((content=isr.read())!=-1){
System.out.print((char)content);
}
}
// 一行一行读, 一行一行写 :
// 字符流只能操作字符数据.
// BufferedReader & BufferedWriter
public static void main(String[] args) throws IOException {
// 一行一行读
// BufferedReader reader = new BufferedReader(new FileReader("demo4.txt"));
BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream("demo2.txt"),"UTF-8"));
String str=null;
while((str=br.readLine())!=null){
System.out.println(str);
}
br.close();
}
private static void outputStreamWriter() throws IOException {
// UTF-8 -> new OutputStreamWriter, utf-8 -> new FileOutputStream -> File / String
// 写入三步曲 : 1. 写入 2. 换行 3. 刷新
// 高效写
// GBK -> new FileWriter -> File / String
// BufferedWriter writer = new BufferedWriter(new FileWriter("demo4.txt"));
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("demo2.txt"), "UTF-8"));
bw.write("How old are you");
bw.write("怎么老是你");
bw.flush();
bw.close();
}
public static void main(String[] args) throws Exception {
File src = new File("C:\\Users\\User\\Desktop\\Day11\\课程资料");
File dest = new File("C:\\Users\\huming\\Desktop");
copyDirectory(src, dest);
System.out.println("执行完成");
}
private static void copyDirectory(File src, File dest) throws Exception {
String name = src.getName();
File folder = new File(dest, name); // C:\Users\Jack\Desktop\课程资料
// 判断并创建
if (folder.exists() == false) {
// 没有, 需要创建文件夹
folder.mkdirs();
}
File[] listFiles = src.listFiles();
for(File f:listFiles){
if(f.isDirectory()){
//是一个文件夹
copyDirectory(f, folder);
}else{
//创建字节流复制文件
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(f));
File file = new File(folder, f.getName());
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file));
int len=-1;
byte[] b=new byte[1024];
while((len=bis.read(b))!=-1){
bos.write(b, 0, len);
}
bos.close();
bis.close();
}
}
}
public static void main(String[] args) throws Exception {
// Properties 属性集对象 (偶尔用到) -> ResouceBunble 资源包类
/*
* Properties 和 Map 的区别 :
* 1. Properties 对象的 K,V 都是 String 类型的, 不能修改.
* 2. Map 的 K,V, 程序员可以自定义.
*
* Properties 格式: key=value
*/
// Properties 作为 `配置文件` 来使用. 写入基本都是手动创建文件, 并实现数据输入.
// 1. 创建一个属性集对象 .properties
Properties prop=new Properties();
// 将数据存储到 `属性集` 对象中
prop.setProperty("歌神", "张学友");
prop.setProperty("舞王", "郭富城");
prop.setProperty("小鲜肉", "黎明");
prop.setProperty("综合", "刘德华");
prop.store(new FileWriter("prop.properties"),"four big sky start");
}
public static void main(String[] args) throws IOException {
// 1. 创建一个 Properties 对象
Properties prop = new Properties();
// 2. 将数据加载到 prop 对象中
prop.load(new FileReader("prop.properties"));
// 3. 取出每一个数据
Set<String> names=prop.stringPropertyNames();
for(String name:names){
String value = prop.getProperty(name);
System.out.println(value);
}
}
public static void main(String[] args) throws IOException {
/*
* 提问 : 数据写入到文件中, 哪些数据可以写入呢 ??? String, Integer
*
* 将对象写入到文件中 : Student, Person
*
* 序列化 : 将Java对象写入到文件中. ObjectOutputStream (对象输出流) -> 序列化
* 反序列化 : 将文件中的对象读取为Java程序中的对象. ObjectInputStream (对象输入流) -> 反序列化
*/
// 1. 创建一个序列化对象
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("stu.txt"));
// 序列化 : 将对象写入到文件中 `数据库`
// NotSerializableException 不可序列化异常 Student 类不可以序列化
oos.writeObject(new Student("柳岩",18));
oos.close();
System.out.println("执行完成");
}
public static void main(String[] args) throws IOException, ClassNotFoundException {
// 反序列化 :
// 1. 创建一个反序列化对象
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("stu.txt"));
// InvalidClassException 非法类异常
// serialVersionUID 序列版本号不兼容 不提供版本号, 每一次运行该版本号就会自动根据该类的源代码生成.
Object object = ois.readObject();
ois.close();
System.out.println(object);
}
1. "流总结 :"
2.
3. 1. "字节流 :"
4. InputStream : 字节输入流
5. |-- FileInputStream
6. |-- BufferedInputStream
7. OutputStream : 字节输出流
8. |-- FileOutputStream
9. |-- BufferedOutputStream
10.
11. 2. "字符流 :"
12. Reader : 字符输入流
13. |-- InputStreamReader : InputStream + 任意编码表
14. |-- FileReader : InputStreamReader + 默认编码表 (便捷类) 字节流 + 编码表
15. |-- BufferedReader
16. Writer : 字符输出流
17. |-- OutputStreamWriter : OutputStream + 任意编码表
18. |-- FileWriter : OutputStreamWriter + 默认编码表 (便捷类)字节流 + 编码表
19. |-- BufferedWriter
20.
21. "请问 : 如何选择使用那种流呢?"
22.
23. "问题1 : 读取字节数据还是字符数据?"
24. 字节 : BufferedInputStream / BufferedOutputStream
25. 字符 : BufferedReader / BufferedWriter
26. "问题2 : 我是读还是写?"
27. 读 : BufferedInputStream / BufferedReader
28. 写 : BufferedOutputStream / BufferedWriter
29. "问题3 : 是否需要高效缓冲呢 ?" 当然要.
30. 是 : 就用对应体系的缓冲流包起来. 缓冲流就是用来装饰其它流的.所以,要加缓冲,就用缓冲流包装其它流.
31. "问题4 : 是否需要指定编码表?"
1. 需要 , 转换流. OutputStreamWriter & InputStreamReader
不需要 , 便捷流. FileWriter & FileReader
多线程就是指一个应用程序中有多条并发执行的路径,每条路径都被称为一个线程,它们会交替执行,彼此间可以进行通信.
并发 concureent:一段时间内,多个线程同时运行。(CPU的高速切换)
总结 : 在一个操作系统中, 每个独立执行的程序都可称为一个进程, 也就是 正在运行的程序
.
简介 : 线程不是进程,但其行为很像进程, 线程是比进程更小的执行单位,一个进程在其执行过程中, 可以产生多个线程, 形成多条执行线索, 每条线索, 即每个线程也有它自身的产生, 存在和消亡的过程. 和进程可以共享操作系统的资源类似, 线程间也可以共享进程中的某些内存单元(包括代码与数据), 并利用这些共享单元来实现数据交换, 实现通信与必要的同步操作, 但与进程不同的是, 线程的中断与恢复可以更加节省系统的开销.具有多个线程的进程能更好地表达和解决现实世界的具体问题,多线程是计算机应用开发和程序设计的一项重要的使用技术.
概况 : 没有进程就不会有线程, 就像没有操作系统就不会有进程一样. 通俗地讲, 线程是运行在进程中的 小进程
.
进程 : Window系统自动分配.
线程 : 程序代码进行分配.
问题 : 线程是不是越多越好呢?
对于一个CPU而言,同一时刻,只能执行一个任务。
同一时间段内,如果线程过多,每个线程被切到的时间就变少了。因此, 线程并不是越多越好。
主线程 (main线程)
Java语言的一大特性就是内置对多线程的支持.
每个运行的程序都是一个进程,当一个Java程序启动时,就会产生一个进程, 该进程会默认创建一个线程. 我们已经知道, Java应用程序总是从主类的main方法开始执行. 当JVM加载代码, 发现main方法之后, 就会启动一个线程, 这个线程被称为 主线程
. 该线程负责执行 main 方法, 那么, 在main方法的执行中再次创建的线程, 就称为程序中的其它线程.如果main方法中没有创建其它的线程,那么当main方法执行完最后一条语句,即main方法返回时,JVM就会结束我们的Java应用程序.如果main方法中又创建了其它线程,那么JVM就要在主线程和其它线程之间轮流切换,保证每个线程都有机会使用CPU资源,main方法即使执行完最后的语句, JVM也不会结束Java的应用程序, JVM一直要等到Java应用程序中的所有线程都结束之后,才结束Java应用程序.
说明 : 多条线程, 看似是同时执行的, 其实不然, 它们和进程一样, 也是由CPU轮流执行的
// 线程初体验 : 同一个Java程序执行多个无限循环. (获取和设置线程名称)
public static void main(String[] args) {
HelloThread hello = new HelloThread();
hello.start();
WorldThread world = new WorldThread();
world.start();
//主线程
while(true){
System.out.println("main ...");
}
}
class HelloThread extends Thread{
@Override
public void run() {
while(true){
System.out.println(this.getName()+"hello");
}
}
}
class WorldThread extends Thread{
@Override
public void run() {
while(true){
System.out.println(this.getName()+"world");
}
}
}
总结 : 使用多线程技术实现了在一个Java应用程序中同时执行两个或两个以上的无限循环.
请问 : 如何创建并启动一个线程呢?
Java提供了两种多线程实现的方式.
1. 继承java.lang包下的Thread类, 重写Thread类的run方法.在run()方法中实现运行在线程上的代码.
2. 实现java.lang.Runnable接口, 同样是在run()方法中实现运行在线程上的代码.
面试题:start方法和run方法的区别?
run:封装线程 任务
. 不调用系统资源,开辟新线程.
start:先调用系统资源,启动线程,再执行run方法。
/*
* 获取和设置线程名称 :
* Thread 类的子类 : 子类对象.getName();
* 非线程类 :Thread.currentThread().getName();
* 主线程名称 : main
* 新开辟的线程名称为 : Thread-0,Thread-1 ... 递增顺序.
*
* 自定义设置线程名称的方式一 :
* 在构造方法中, 主动调用 Thread 类的 Thread(String name)构造方法设置线程名称
* 自定义设置线程名称的方式二 :
* 调用 Thread 类的 setName(String name)方法, 设置线程名称
*/
public static void main(String[] args) {
Test t1 = new Test("线程一");
t1.start();
Test t2 = new Test("线程二");
t2.start();
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + " -> run ...");
}
}
class Test extends Thread {
public Test(String name) {
super(name);
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(this.getName() + " -> run ...");
}
}
}
在程序中当某个线程发生了异常,那么当前这个线程就会自动停止运行,这个线程就自动的结束。只要其他线程没有发生问题,依然会正常的运行。直到线程正常的把run方法中的所有代码执行完成,这个线程才会结束。
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + " -> run ...");
// 主线程抛出异常, 不会影响其它线程执行...
int result=10/0;
}
继承Thread优点小结 :
使用 Thread 子类创建线程的优点是: 可以在子类中增加新的成员变量, 使线程具有某种属性, 也可以在子类中增加新的方法, 使线程具有某种功能. 但是, Java不支持多继承, Thread 类的子类不能在扩展其他的类.
实现Runnable接口
说明 : 创建线程的另一个途径就是用Thread类直接创建线程对象. 使用Thread创建线程通常使用的构造方法是: Thread(Runnable target); 该构造方法中的参数是一个 Runnable 类型的接口. 因此, 在创建线程对象时必须向构造方法的参数传递一个实现 Runnable 接口类的实现类对象. 线程绑定 Runnable 接口, 也就是说, 当线程被调度并转入运行状态时, 所执行的就是 run() 方法中所规定的操作. (接口回调)
我们发现,Thread类中的run方法就是实现自Runnable接口。
run方法是用来封装线程任务的。Runnable接口中,只有一个run方法,因此,这个接口就是专门用来封装线程任务的接口。
因此,实现该接口的类,称为线程任务类。
观察 : Thread类中的run() 方法就是实现了 Runnable 接口中的run()方法.
方式1:自定义类继承Thread类,重写run方法,调用start启动线程,会自动调用我们线程类中的run方法。
方式2:自定义类,实现Runnable接口,把任务对象传给Thread对象。调用Thread对象的start方法,执行Thread的run。那么为什么最后执行的是任务类中的run呢?
分析 :
1. 在我们创建 Thread 类对象时,我们已经将 task 任务类的对象作为参数传递给了线程类对象.在其内容就会将 task 赋值给内部属性 target 进行存储.
2. 当我们调用 Thread 对象的 start 方法启动线程时,肯定会执行 Thread 类的 run 方法.而 run 方法的实现如下 :
@Override
public void run() {
if (target != null) {
target.run();
}
}
1. 在 Thread 的 run 方法中, 会先判断 target 是否为 null. 这个target就是我们创建 Thread 对象时传入的任务类对象,所以 target 不为 null, 因此就会执行 target 的 run 方法. 也就是任务类的 run 方法.
使用接口完成多线程的好处
好处一 :避免了Java单继承带来的局限性
好处二 :适合多个相同程序代码的线程去处理同一个资源的情况, 更灵活的实现数据的共享。
说明 : 在之前的售票案例中, 极有可能碰到 意外
情况, 如一张票被打印多次, 或者打印出的票号为0, 甚至负数. 这些 意外
都是由多线程操作共享资源tickets 所导致的线程安全问题.
原因分析:多个线程操作共享资源. CPU高速切换.
上述错误的编号,是因为有多个线程,并且多个线程同时在操作同一个变量。
出现的错误票号:
是多个线程在执行售票的任务的时候,由于在售票的代码中访问了同一个成员变量tickets。可是在操作tickets的这些语句中,一个线程操作到其中的一部分代码的时候,CPU切换到其他的线程上开始执行代码。这样就会导致tickets变量中的值被修改的不一致。
上述的这些错误,我们称为多线程运行的安全问题。
总结多线程的安全问题发生的原因:
1. 首先必须有多线程。
2. 多个线程在操作共享的数据,并且对共享数据有修改。
3. 本质原因是CPU在处理多个线程的时候,在操作共享数据的多条代码之间进行切换导致的。
为了实现这种限制, Java中提供了同步机制, 当多个线程使用同一个共享资源时, 可以将处理共享资源的代码放置在一个代码块中, 使用Synchronized关键字来修饰. 被称作同步代码块.
问题1:锁对象是什么?
任意一个对象.
问题2:哪些代码需要被同步??
操作资源的所有代码
上述的这个解决方案:称为线程的同步。
要想保证线程的安全:需要在操作共享数据的地方,加上线程的同步锁。
锁对象的前提条件 : 必须要保证 锁对象
的唯一性.
注意 : 同步代码块中的锁对象可以是任意类型的对象,但多个线程共享的锁对象必须是唯一的. 任意
说的是共享锁对象的类型, 所以, 锁对象的创建代码不能放在 run() 方法中.否则每个线程运行到 run()方法都会创建一个新对象,这样每个线程都会有一个不同的锁, 每个锁都有自己的标志位. 线程之间便不能产生同步的效果.
注意事项 :
A:同步代码块中,只能包含操作资源的代码。不能乱包。
B:同步代码块的锁可以是任意的。但是必须是唯一的。
补充说明 : lock是一个锁对象, 它是同步代码块的关键.当线程执行同步代码块时, 首先会检查所对象的标志位, 默认情况下标志位为1, 此时线程会执行同步代码块, 同时将锁对象的标志位设置为0. 当一个新的线程执行到这段同步代码块时,由于锁对象的标志位为0, 新线程会发生阻塞, 等待当前线程执行完同步代码块后, 锁对象的标志位设置1,新线程才能进入同步代码块执行其中的代码, 循环往复, 直到共享资源被处理完为止. 这个过程就好比一个 公用电话亭
, 只有前一个人打完电话出来后, 后面的人才可以进入电话亭拨打电话.
同步方法
说明 : 通过同步代码块我们了解到可以有效解决线程的安全问题, 当把共享资源的操作放在Synchronized定义的区域内时, 便为这些操作加了同步锁.
同步方法 : 在方法前面同样可以使用Synchronized关键字来修饰, 被修饰的方法称为 同步方法
, 它能实现和同步代码块同样的功能.
思考 : 大家可能会有这样的疑问 : 同步代码块的锁是自己定义的任意类型的对象, 那么同步方法是否也存在锁? 如果有, 它的锁是什么呢?
答案是肯定的, 同步方法也有锁, 它的锁就是当前调用该方法的对象, 也就是this指向的对象.
这样做的好处是, 同步方法被所有线程所共享, 方法所在的对象相对于所有线程来说是唯一的, 从而保证了锁的唯一性. 当一个线程执行该方法时, 其它的线程就不能进入到该方法中, 直到这个线程执行完该方法为止, 从而达到了线程同步的效果.
注意:
1:如果一个方法内部,所有代码都需要被同步,那么就用同步方法
2:同步方法的锁是this
JDK5提供的Lock接口比JDK5之前的同步更好使用。Lock接口代替JDK5之前的同步代码块. 更加灵活.
注意 : Lock接口的实现类对象不能和 Object类中的 wait(), notify(), notifyAll(), 共同使用, 因为Object类中的 wait(), notify(), notifyAll(), 方法必须要和 同步锁 synchronized 共同使用, 否则报异常!
面试题
public static void main(String[] args) {
/*
new Thread(){
@Override
public void run() {
for(int i=0;i<100;i++){
System.out.println("匿名子类 -> run ... " + i);
}
}
}.start();
new Thread(new Runnable() {
@Override
public void run() {
for(int i=0;i<100;i++){
System.out.println("匿名实现类 -> run ... " + i);
}
}
}).start();*/
// 匿名子类 + 匿名实现类
/*
* 匿名子类 : 子类已经重写了父类的run方法, 程序运行时, 执行子类重写的方法.
*
* 说明 : 子类如果不重写 Thread 类的 run 方法, 程序会执行 Thread 父类的 run 方法.
* 由于匿名实现类对象作为参数传入, Thread 类的内部 target = 匿名实现类对象
*
* target != null target.run(); 执行匿名实现类的 run 方法.
*/
new Thread(new Runnable() {
@Override
public void run() {
for(int i=0;i<100;i++){
System.out.println("匿名实现类 -> run ... " + i);
}
}
}){
@Override
public void run() {
for(int i=0;i<100;i++){
System.out.println("匿名子类 -> run ... " + i);
}
}
}.start();
// 主线程代码...
for (int i = 0; i < 100; i++) {
System.out.println("main -> run ... " + i);
}
}
InetAddress 类的工厂方法 : (静态方法)
InetAddress 类没有明显的构造方法, 为生成一个InetAddress对象, 必须运用一个可用的工厂方法. 工厂方法仅是一个类中静态方法返回一个该类实例的约定, 对于 InetAddress, 三个方法 getLocalHost(), getByName() 以及 getAllByName() 可以用来创建 InetAddress 的实例.
在Internet 上. 用一个名称来代表多个机器是常用的事, getAllByName() 方法返回代表由一个特殊名称分解的所有地址的InetAddress类数组. 在不能把名称分解成至少一个地址时, 它将引发一个UnknownHostException异常
网络通信使用 IP地址标识网络中的一台计算机, 使用端口号标识计算中的进程(程序).
进程号 : 系统分配的.
端口号 : 程序分配的.
一个进程中可以分配多个端口
在所示的 TCP/IP 结构时, 提到传输层的两个重要的高级协议, 分别是 UDP 和 TCP 协议, 其中 UDP 是 User Datagram Protocol 的简称, 称为用户数据报协议, TCP 是 Transmission Control Protocol 的简称, 称为传输控制协议.
UDP 是无连接通信协议, 即在数据传输时, 发送端和接收端不建立逻辑连接. 简单来说, 当一台计算机向另外一台计算机发送数据时, 发送端不会确认接收端是否存在, 就会发出数据. 同样接收端在接收数据时, 也不会向发送端反馈是否收到数据.
特点 : 两端都相同, 可以发送数据, 同时也可以接收数据. 无明显的差异.
public static void main(String[] args) throws IOException {
// `发` 和 `收` 两个任务不能互相阻塞.
// 1. 创建一个码头 (码头既可以发送也可以接收)
DatagramSocket datagramSocket = new DatagramSocket(10086);
// 2. 先执行发送任务
// 2.1 创建一个发送任务类对象
SendTask sendTask = new SendTask(datagramSocket, "localhost", 10086);
// 2.2 为了让任务在子线程执行, 创建一个线程类对象, 将任务类对象作为参数传入
Thread sendThread = new Thread(sendTask,"发送任务");
// 2.3 启动发送线程
sendThread.start();
// 3. 再执行接收任务
ReceiveTask receiveTask = new ReceiveTask(datagramSocket);
// 3.1 创建一个接收任务类对象
// 3.2 为了让任务在子线程执行, 创建一个线程类对象, 将任务类对象作为参数传入
Thread receiveThread = new Thread(receiveTask,"接收任务");
// 3.3 启动接收线程
receiveThread.start();
}
class ReceiveTask implements Runnable{
private DatagramSocket datagramSocket;
public ReceiveTask(DatagramSocket datagramSocket){
this.datagramSocket=datagramSocket;
}
@Override
public void run() {
byte[] buf=new byte[1024];
DatagramPacket datagramPacket = new DatagramPacket(buf, buf.length);
while(true){
try{
datagramSocket.receive(datagramPacket);
byte[] data = datagramPacket.getData();
int length = datagramPacket.getLength();
String str = new String(data, 0, length);
String ip = datagramPacket.getAddress().getHostAddress();
System.out.println(ip+"----"+str);
}catch(Exception e){
e.printStackTrace();
}
}
}
}
class SendTask implements Runnable{
private DatagramSocket datagramSocket;
private String ip;
private Integer port;
public SendTask(DatagramSocket datagramSocket, String ip,Integer port){
this.datagramSocket=datagramSocket;
this.ip=ip;
this.port=port;
}
@Override
public void run() {
Scanner sc = new Scanner(System.in);
String line = null;
while((line=sc.nextLine())!=null){
byte[] buf = line.getBytes();
InetAddress address;
try {
address = InetAddress.getByName(ip);
DatagramPacket datagramPacket = new DatagramPacket(buf, buf.length, address,port);
datagramSocket.send(datagramPacket);
} catch (Exception e) {
e.printStackTrace();
}
}
sc.close();
}
}
TCP协议是面向连接的通信协议, 即在传输数据前先在发送端和接收端建立逻辑连接, 然后再传输数据, 它提供了两台计算机之间可靠无差错的数据传输. 在TCP连接中必须要明确客户端和服务器端, 每次连接的创建都需要经过 三次握手
, 第一次握手, 客户端向服务端发出连接请求, 等待服务器确认; 第二次握手, 服务器向客户端回送一个响应, 通知客户端收到了连接请求; 第三次握手, 客户端再次向服务器端发送确认信息, 确认连接.
由于TCP协议的面向连接性, 它可以保证传输数据的安全性, 所以是一个被广泛采用的协议, 例如在下载文件时, 如果数据接收不完整, 将会导致文件数据丢失而不能打开, 因此, 下载文件时必须采用TCP协议.
两种传输协议的简单对比 :
1. 使用UDP时, 每个数据包中都给出了完整的地址信息, 因此不需要建立发送方和接收方法的连接.
2. 对于 TCP 协议, 由于它是一个面向连接的协议, 在Socket之间进行数据传输之前必然要建立连接. 所以在TCP中多了一个连接的建立时间.
3. 使用UDP传输数据时是有大小限制的, 每个被传输的数据报必须限定在 64KB 之内.
4. TCP没有这方面的限制, 一旦连接建立起来, 双方的socket就可以按统一的格式传输大量的数据.
5. UDP是一个不可靠的协议, 发送方所发送的数据报不一定以相同的次序到达接收方.
6. TCP是一个可靠的协议, 它确保接收方完全正确地获取发送方所发送的全部数据.
UDP 无连接, 效率高.
TCP 有连接, 安全高.
TCP实现多线程图片上传
/*
* TCP 客户端 :
* 数据源 : 硬盘路径
* 目的地 : socket 通道.
*/
public class TCPClientUploadDemo2 {
public static void main(String[] args) throws IOException {
// 1. 创建一个客户端的套接字
Socket socket = new Socket("192.168.48.101",10010);
// 2. 先读后写 (复制) -> 字节流
// 2.1 创建一个高效的缓冲字节输入流 (硬盘路径)
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("C:\\Users\\User\\Desktop\\hello\\1.jpg"));
// 2.2 创建一个高效的缓冲字节输出流 (socket 通道)
BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());
// 2.3 读写
int len=-1;
byte[] b=new byte[1024];
while((len=bis.read(b))!=-1){
bos.write(b, 0, len);
bos.flush();
}
// 关闭
socket.shutdownOutput();
// 2.4 读取服务器反馈的数据
int receiveLen=-1;
byte[] receiveBuf=new byte[1024];
InputStream in = socket.getInputStream();
while((receiveLen=in.read(receiveBuf))!=-1){
String str = new String(receiveBuf, 0, receiveLen);
System.out.println("服务器返回的数据为: " + str);
}
// 3. 关闭套接字
bis.close();
socket.close();
}
}
public class TCPServerUploadDemo3 {
public static void main(String[] args) throws Exception {
// 1. 创建一个服务端套接字
ServerSocket serverSocket = new ServerSocket(10010);
// 2. 等待客户端连接
Socket socket = serverSocket.accept();
UploadPictureTask uploadPictureTask=new UploadPictureTask(socket);
Thread uploadThread=new Thread(uploadPictureTask,"上传线程");
uploadThread.start();
serverSocket.close();
}
}
class UploadPictureTask implements Runnable{
private Socket socket;
public UploadPictureTask(Socket socket){
this.socket=socket;
}
@Override
public void run() {
try{
// 3. 读写操作 (复制) -> 字节流
// 3.1 输入流 (socket 通道)
BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());
// 3.2 输出流 (硬盘路径)
File parentFile = new File("C:\\Users\\huming\\Desktop\\hello");
if(parentFile.exists()==false){
parentFile.mkdirs();
}
String name = UUID.randomUUID().toString().replaceAll("-", "");
File file = new File(parentFile,name+".jpg");
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file));
// 3.3 读写操作
int len=-1;
byte[] b=new byte[1024];
while((len=bis.read(b))!=-1){
bos.write(b, 0, len);
bos.flush();
}
// 3.4 给客户端回送一个反馈 (输出流)
OutputStream out = socket.getOutputStream();
out.write("成功".getBytes());
out.flush();
socket.shutdownOutput();
// 4. 关闭
bos.close();
}catch(Exception e){
e.printStackTrace();
}
}
类加载器
类加载器:类加载器是负责加载类的对象。将class文件(硬盘)加载到内存生成Class对象。
所有的类加载器 都是 java.lang.ClassLoader 的子类
类的加载说明 :
类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个 java.lang.Class对象,用来封装类在
方法区内的数据结构.
/*
* Private constructor. Only the Java Virtual Machine creates Class objects.
* This constructor is not used and prevents the default constructor being
* generated.
*/
private Class(ClassLoader loader) {
// Initialize final field for classLoader. The initialization value of non-null
// prevents future JIT optimizations from assuming this final field is null.
classLoader = loader;
}
注意 : 只有Java虚拟机才能创建class类的对象.
重点 : 类加载的最终产品是位于堆区中的 Class对象.
Class对象封装了类在方法区内的数据结构 ,并且向Java程序员提供了访问方法区内的数据结构的接口,这些接口都定义在了 反射
包中.
@Test
public void demo05(){
// 应用类加载器
ClassLoader loader = ClassLoaderDemo1.class.getClassLoader();
System.out.println(loader);
// 引导类加载器
ClassLoader loader2 = loader.getParent();
System.out.println(loader2);
//系统类加载器
ClassLoader loader3 = loader2.getParent();//null
System.out.println(loader3);
}
@Test
public void demo04(){
// java.class.path : 应用类加载器的加载路径
// 自己写的项目中的类, 框架中的类, 组件类.
String path = System.getProperty("java.class.path");
System.out.println(path);
ClassLoader loader = ClassLoaderDemo1.class.getClassLoader();
// sun.misc.Launcher$ AppClassLoader 应用类加载器 @605df3c5
System.out.println(loader);
}
@Test
public void demo3(){
//java.ext.dirs
String path = System.getProperty("java.ext.dirs");
System.out.println(path);
//DNSNameService
ClassLoader loader = DNSNameService.class.getClassLoader();
//sun.misc.Launcher$ExtClassLoader@12a3a380
System.out.println(loader);
}
@Test
public void demo2(){
//sun.boot.class.path : 引导类加载器的加载路径
// rt->runtime 运行时
String path = System.getProperty("sun.boot.class.path");
System.out.println(path);
// 获取一个类的类加载器 . 类名.class.getClassLoader();
ClassLoader loader = String.class.getClassLoader();
System.out.println(loader); //null
}
/*
* System 系统的加载路径
*
* sun.boot.class.path : 引导类加载器的加载路径
* java.ext.dirs : 扩展类加载器的加载路径
* java.class.path : 应用类加载器的加载路径
*
*/
// 如何获取系统的属性集 ???
@Test
public void demo1(){
Properties prop = System.getProperties();
Set<String> names = prop.stringPropertyNames();
for(String name:names){
String value = prop.getProperty(name);
System.out.println(name+"="+value);
}
}
从虚拟机的角度来说,只存在两种不同的类加载器:一种是启动类加载器(Bootstrap ClassLoader),该类加载器使用C++语言实现,属于虚拟机自身的一部分。另外一种就是所有其它的类加载器,这些类加载器是由Java语言实现,独立于JVM外部,并且全部继承自抽象类java.lang.ClassLoader。
类加载器之间的这种层次关系,就称为类加载器的双亲委派模型(Parent Delegation Model)。该模型要求除了顶层的启动类加载器外,其余的类加载器都应当有自己的父类加载器。子类加载器和父类加载器不是以继承(Inheritance)的关系来实现,而是通过组合(Composition)关系来复用父加载器的代码。
双亲委派模型的工作过程为:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的加载器都是如此,因此所有的类加载请求都会传给顶层的启动类加载器,只有当父加载器反馈自己无法完成该加载请求(该加载器的搜索范围中没有找到对应的类)时,子加载器才会尝试自己去加载。
反射概述 :
Java反射机制是在运行状态中,对指定的类,任意的方法或任意的字段进行操作,这种动态获取的信息以及动态调用对象方法的功能称为java语言的反射机制。
简而言之,反射就是:在运行时通过代码操作类。
想要操作类,就必须先获得该类的字节码对象Class 对象。通过Class提供的方法对类进行进一步的解剖,从而实现各种操作。
在进行具体操作之前,大家需要先掌握类各个组成的专业描述
Class类
Constructor 构造方法
Method 方法
Field 字段 / 属性
instance 实例 / 实例对象
invoke 调用, 执行
usb.properties
usb1=cn.itcast.computer.Mouse
usb2=cn.itcast.computer.Keyboard
public class ComputerDemo {
private static Properties prop;
static{
prop=new Properties();
try {
prop.load(new FileInputStream("src\\usb.properties"));
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws Exception {
Computer computer = new Computer();
computer.run();
for(int i=1;i<=prop.size();i++){
// 外接设备
// 1. 获取 prop 对象中 usb 对应的 className
String className = prop.getProperty("usb"+i);
// 2. 反射该对象
Class<?> cls = Class.forName(className);
// 3. 创建对象
USB usb = (USB)cls.newInstance();//将反射得到的对象强制转换成对象实现的接口的类型
// 4. 调用外接设备
computer.useUSB(usb);
}
/*
Mouse m = new Mouse();
computer.useUSB(m);
Keyboard k = new Keyboard();
computer.useUSB(k);*/
}
}
反射综合案例 – 举办晚会
public class PartyFactory {
private static Properties prop;
// 静态代码块 : 加载配置文件
// 静态代码块在程序运行期间仅会被执行一次, 而且是在类加载的同时的执行的.
static{
prop = new Properties();
try {
prop.load(new FileInputStream("src\\party.properties"));
} catch (IOException e) {
e.printStackTrace();
}
}
public static Singable getSingable() throws IOException, Exception {
// 从 prop 对象中获取数据
String className = prop.getProperty("Singable");
// 使用 Class 反射该对象
// Class.forName("cn.itcast.party.ZhangXueYou");
Class<?> cls = Class.forName(className);
// 使用 cls 对象调用 newInstance 方法, 创建该字符串表示的实际对象
Object obj = cls.newInstance();
// 返回使用反射创建的对象
return (Singable) obj;
}
@SuppressWarnings("unchecked")
public static <T> T getInstance(Class<T> clss) throws Exception {
String interfaceName = clss.getSimpleName(); // key -> interfaceName
// 从 prop 对象中获取数据
String className = prop.getProperty(interfaceName); // value -> className
// 使用 Class 反射该对象
// Class.forName("cn.itcast.party.ZhangXueYou");
Class<?> cls = Class.forName(className);
// 使用 cls 对象调用 newInstance 方法, 创建该字符串表示的实际对象
T t = (T) cls.newInstance();
// 返回使用反射创建的对象
return t;
}
}
public class EveningPartyDemo {
//面向对象编程
/*
* Singable sing = new ZhangXueYou();
sing.sing();
*/
/*
* 此种方案也不是最好的方案 因为这样写的话 需要对配置文件中的每一项配置来一次单独的调用 在工厂类中需要对每个配置来一个单独的方法 太冗余不灵活
// 主业务逻辑的对象应该交给一个 `独立的类` 进行管理. -> 晚会工厂类 (造对象) PartyFactory 工厂设计模式
// 来一个会唱歌的.
Singable s = PartyFactory.getSingable();
s.sing();
*/
/*
System.out.println(Singable.class);//interface cn.itcast.party.Singable
System.out.println(Singable.class.getName());//cn.itcast.party.Singable
System.out.println(Singable.class.getSimpleName());//Singable
*/
public static void main(String[] args) throws Exception {
Singable instance = PartyFactory.getInstance(Singable.class);
instance.sing();
// 来一个会跳舞的.
/*
Dancable d = PartyFactory.getInstance(Dancable.class);
d.dance();
// 来一个会表演的.
Performable p = PartyFactory.getInstance(Performable.class);
p.perform();
*/
}
}
注解
Annotation 其实就是代码里的特殊标记, 有了注解技术后,开发人员可以通过注解告诉类如何运行。在Java技术里注解的典型应用是:可以通过反射技术去得到类里面的注解,以决定怎么去运行类。
掌握注解技术的要点:
1. 如何定义注解
2. 如何反射注解,并根据反射的注解信息,决定如何去运行类
动态代理
代理 : 对被代理对象的拦截和控制.
public interface SuperStar {
void sing(int money);
void liveShow(int money);
void sleep();
}
public class LiuYan implements SuperStar{
@Override
public void sing(int money) {
System.out.println("唱了一首 <<真的真的很爱你>> 歌曲.");
System.out.println("挣了 : " + money);
}
@Override
public void liveShow(int money) {
System.out.println("参加了 <<Running Man>> 节目.");
System.out.println("挣了 : " + money);
}
@Override
public void sleep() {
System.out.println("真的真的很累了, 休息一下! Zzzz~~~");
}
}
public class SuperStarDemo {
@Test
public void demo1() {
// 1. 创建一个 `柳岩` 对象
LiuYan ly = new LiuYan();
// 2. 调用柳岩的方法
ly.sing(100); // 10000 块
ly.liveShow(200); // 100000 块
ly.sleep();
}
// 动态代理的作用 : 拦截和控制被代理对象的所有方法.
@Test
public void demo2() {
// 1. 创建一个 `柳岩` 对象
// JDK 7.0 版本. JDK 8.0 版本 final 关键字可以不用书写.
final LiuYan ly = new LiuYan();
/*************** 动态代理 start ***************/
ClassLoader loader = ly.getClass().getClassLoader();
Class<?>[] interfaces = ly.getClass().getInterfaces();
// JDBC Java数据库连接 -> 连接池 (自定义数据库连接池) 用到动态代理 -> 数据库连接池框架 (DBCP, c3p0 框架)
SuperStar proxy = (SuperStar)Proxy.newProxyInstance(loader, interfaces, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
//System.out.println(method.getName());//sing liveShow sleep
//System.out.println(Arrays.toString(args));
//return method.invoke(ly, args);
// 2. 判断调用的方法
if ("sing".equals(method.getName())) {
int money = (int) args[0];
// 2.1 判断 money 的金额
if (money < 10000) {
// 条件成立, 啥都不执行
System.out.println("滚, 你这个穷屌丝, 赶紧回家撸代码去...");
return null;
} else {
System.out.println("代理抽取了 : " + (money * 0.3) + " 金额.");
return method.invoke(ly, (int)(money * 0.7));
}
}/* else if ("liveShow".equals(method.getName())) {
int money = (int) args[0];
// 2.2 判断 money 的金额
if (money < 100000) {
// 条件成立, 啥都不执行
System.out.println("滚, 怎么又是你这个穷屌丝, 回家继续撸代码去...");
return null;
} else {
System.out.println("代理抽取了 : " + (money * 0.3) + " 金额.");
return method.invoke(ly, (int)(money * 0.7));
}
}*/
// 不拦截和控制, 直接调用对象的方法
return null;
}
});
/*************** 动态代理 end ***************/
// 2. 调用 `代理` 的方法 proxy
proxy.sing(10000); // 10000 块
proxy.liveShow(200000); // 100000 块
proxy.sleep();
}
}
代理抽取了 : 3000.0 金额.
唱了一首 <<真的真的很爱你>> 歌曲.
挣了 : 7000
xml
略
BeanUtils
JavaBean 介绍 :
JavaBean 它是一个简单的Java类, 这个类拥有 get 和 set 方法即可. 但要求这个类必须有一个公开的空参构造方法.
后期开发中, 这个类一般会保存在名为 domain 或者 beans 包中.
BeanUtils 类的功能是把一个Map集合中的数据, 封装到一个 JavaBean 上. 使用BeanUtils工具类中的populate方法就可以把Map集合中的数据封装到指定
的bean上.Map中的key名称必须和bean对象的属性名称保持一致.
Request 请求 -> getParameter -> getParameterMap(); -> populate 填充 -> JavaBean 类的对象.
BeanUtils 作用 :
可以为JavaBean的属性设置值, 获取值, 并通过Map为JavaBean的数值赋值.
BeanUtils 是 Apache commons组件的成员之一,主要用于简化JavaBean封装数据的操作。它可以给JavaBean封装一个字符串数据,也可以将一个表单提交的所有数据封装到JavaBean中。
使用第三方工具,需要导入jar包:commons-beanutils-1.9.3.jar commons-logging-1.2.jar
封装表单数据,使用Map 模拟 request.getParameterMap()
class Request{
public static Map getParamterMap(){
Map<String, Object> map = new HashMap<String,Object>();
map.put("name", "Jack");
map.put("age", 18);
map.put("gender", '男');
map.put("score", "99");
return map;
}
}
public void demo4() throws Exception {
Object obj = new Student();
BeanUtils.populate(obj, Request.getParamterMap());
System.out.println(obj);
}
解析 xml 并实现为 JavaBean 对象赋值 :
public class BeanUtilsDemo2 {
@Test
public void demo2() throws Exception {
// 1. SAXReader
SAXReader reader = new SAXReader();
// 2. read
Document document = reader.read(new File("students.xml"));
// 3. rootElement
Element root = document.getRootElement();
// 4. studentElements
List<Element> students = root.elements();
// 定义一个 Student 类型的对象
Student stu;
// 定义一个 list 集合
ArrayList<Student> list = new ArrayList<Student>();
// 5. 遍历
for(Element student:students){
stu=new Student();
// 6. 属性
String stuNo = student.attributeValue("stuNo");
System.out.println(stuNo);
BeanUtils.setProperty(stu, "stuNo", stuNo);
List<Element> elements = student.elements();
// 8. 遍历 elements [name, age, gender, score]
for(Element field:elements){
// 9. 属性名
String name = field.getName();
// 10. 属性值
String value = field.getText();
System.out.println(name+value);
// 再次赋值
BeanUtils.setProperty(stu, name, value);
}
list.add(stu);
}
// 查看list
for(Student s:list){
System.out.println(s);
}
}
}
相关文章
- 暂无相关文章
用户点评