文章目录
java基础部分零碎知识总结
基本数据类型
基础知识
- Byte int long short都可以用八进制,十进制 十六进制表示,前缀0表示八进制,前缀0x表示十六进制
- Char类型值的范围是\u0000到\uFFFF,\u表示是十六位的unicode字符,\u0000表示一个空字符,长度为1,空格符为\u0020,十进制等效值为0,\uFFFF等效值为65535
类型之间转换(int和double为例)
- int类型转double类型
- 隐式转换:double比int范围大,所以int转double可以不需要类型转换
int i = 123;
double j = i;
- 使用double.valueOf()方法
double j = Double.valueOf(i);
- double转int类型
- 使用类型转换,损失精度,不四舍五入,直接删去小数点后数字
double i = 123.88;
int j = (int) i;
- 使用Math.round()方法,四舍五入
- 使用double wrapper类的intValue()方法,损失精度,不四舍五入
Double doubleObject = new Double(i); //或者Double object = i; 自动装箱机制
int j = doubleObject.intValue();
局部变量和成员变量(实例变量和类变量\静态变量)
- 访问修饰符不能用于局部变量
- Java变量分为局部变量和成员变量,成员变量分为实例变量(没有static修饰)和类变量(有static)修饰,局部变量声明在方法内,成员变量声明在类之内,方法外;局部变量储存在栈内存中,实例变量储存在对象所在的堆内存中,类变量储存在方法区;局部变量生命周期和方法一样,实例变量生命周期和对象一样,类变量和类一样;类变量初始化后不可改变;实例变量可直接使用变量名访问,在静态方法中需要使用 对象.变量名 来访问,静态变量使用 类名.变量名 访问
包装类
包装类作用
- java面向对象的语言,但是基本数据类型不具备对象的特性,java为每个基本数据类型提供了对应的包装类,包装类创建对象的方式和其他类一样
Integer num = new Integer(0);
自动拆箱、自动装箱
- 基本数据类型向包装类转换
Integer num = new Integer(0);
- 包装类向基本数据类型转换
int num1 = num.intValue();
- 自动装箱拆箱
Integer num = 1; //自动装箱,自动装箱使用的是Integer.valueOf(1)而不是new Integer(1)
int num1 = num; //自动拆箱
缓存机制(JDK1.5之后提供的特性)
- 只有Byte Short Integer Long Character有缓存机制,Float Double Boolean没有该机制
- 只要使用Integer类, Integer静态内部类加载时会创建-128 ~ 127的Integer对象,同时创建一个数组cache来缓存这些对象,当使用valueOf方法时首先判断是否在缓存数组中,如果在就直接范围已经缓存的对象,不会在创建新对象,当使用new创建对象时,就会直接创建新对象
- 使用场景:某个方法需要的参数为包装类型参数,而我们手里只有基本数据类型参数的值,则不需要做任何特殊的处理,直接把这个值传入方法中即可
- 缓存的作用:如果在缓存池中则不需要创建新对象,节省空间开销和时间消耗,提升了性能
包装类 == 和 equals区别
- 包装类使用==判断的是对象是否为同一个对象,而equals则是判断对象的值是否相等
- java中不能重写运算符,但可以重写equals()方法,Object类中默认的equals()方法时判断是否为同一个对象,经过包装类重写后则判断对象的值是否相同,同时还会重写hashcode()方法,使得哈希值等于对象值本身
public boolean equals(Object obj) {
if(obj instance of Integer) {
return value == ((Integer)obj).intValue();
}
return false;
}
面向对象 的理解
- 面向对象思想是一种优秀的程序设计方法,他运用类 对象 封装 继承 消息等概念进行程序设计,面向对象思想从现实世界客观存在的事物出发构造软件系统,在程序设计中尽可能地使用人类的自然思维方式,将这些事物抽象成一个个类,作为系统的基本构成单元,使得软件系统的组件可以映射到现实世界中来,保持了客观事物以及相互关系的本来面貌。
- 结构化程序设计是一种按功能来进行系统需求分析的方法,他的特点有自顶向下 逐步求精 模块化等,它先使用结构化分析方法对系统需求进行分析,然后使用结构化设计方法对软件系统进行整体设计,之后在进行详细设计,最后通过结构化编程方法实现系统。函数是结构化程序设计系统的基本构成单元,每个函数负责一个功能,每个函数都会有一些数据输入,然后经过一系列处理,再输出一些数据。整体流程是作为程序入口的主函数调用其他普通函数,然后普通函数之间再相互调用,完成整个系统功能。
this
- 一个对象一个this
- this是一个变量,是一个引用,this保存当前对象的内存地址,指向自身,this储存在堆内存当中对象的内部
- this只能用在实例方法中
- 哪个对象调用该实例方法,this就代表谁
- 当局部变量和实例变量名称相同时,this不能省略,用于区分二者eg:this.name = name;
- this可以用在构造方法中,也可以省略
- this()用来在一个构造方法中调用类中另一个构造方法,this(参数列表),作用是代码复用,这样使用的话this语句需要是构造器中第一个语句
System.out.println()
- system是一个类名
- out是一个静态变量(对象)
- println()带括号是一个实例方法
- system.out.println(引用);当直接输出一个引用的时候,prinln()方法会自动调用引用.toString(),然后输出引用.toString()的结果
三大特征 – 封装
- 什么是封装
封装实际上就是信息隐藏,利用抽象数据类型将数据和基于数据的操作封装在一起,使其构成一个不可分割的独立实体,数据被保护在抽象数据类型的内部,尽可能隐藏内部的细节,只保留一些简单的接口使其与外界发生联系。 - 封装的功能
- 封装后的代码外部人员不能随意访问,保证数据的安全
- 屏蔽复杂,暴露简单,对于外部调用的人员来说,不需要关心代码的复杂实现,只需要通过一个简单的入口就可以访问
- 封装的步骤:
- 属性私有化(使用private关键字)
- 对外提供简单的操作入口(一个属性对外提供set, get方法,不带static的实例方法,可在set方法中设立关卡)
- 封装的意义
- 封装降低了程序的耦合度
- 封装提高了程序的扩展性、复用性和重用性
三大特征 – 继承
- 继承作用
- 解决代码臃肿的问题,实现代码的复用
- 有了继承才有了方法覆盖和多态机制
- 继承特点
- java虽然不支持多继承,但又间接继承的效果
- 子类继承父类,除构造方法之外,剩余都可 以继承,但是私有属性不能直接访问,可以通过调用继承来的set get方法访问
- 子类继承父类之后,相当于父类的代码复制到子类中,构造方法除外
- 不是代码有重复就可以使用继承,需要看两个类之间是否可以使用A is a B这种形式表示
- 继承缺点
- 代码耦合度变高
- 父类修改子类受到牵连
方法覆盖和方法重载
- 覆盖overwrite或者override,重载overload
- 构成方法重载条件:
- 在同一个类中
- 方法名相同
- 参数列表不同(个数、顺序、类型)
- 构成方法覆盖条件:
- 继承的方法无法满足子类的业务需求,则需要进行方法覆盖
- 两个类有继承关系
- 有相同的方法名、返回值类型、参数列表
- 子类的方法的访问权限只能更高,不能更低(父类public 子类protected不行)
- 重写之后的方法不能比之前抛出更多异常
- 私有方法不能覆盖,构造方法不能继承也不能覆盖
- 方法覆盖只针对实例方法,静态方法覆盖没有意义
- 方法覆盖后,子类对象执行方法一定是子类重写后的方法
三大特征 – 多态
多态基础语法
- 向上转型upcasting:父类型引用指向子类型对象Animal a = new Cat();
- 向下转型downcasting:父 ----> 子,需要添加强制类型转换符Cat c = (Cat) a;
- 不管向上还是向下转型,二者必须有继承关系,编译才会不报错(对于接口不适用)
- 需要调用执行子类对象中特有的属性和方法,必须向下转型才可以调用
- instanceof运算符:引用 instanceof 类型,结果为true或者false
- 向下转型有风险,容易出现ClassCastException,为避免风险,使用instanceof运算符,可以在程序动态运行阶段判断某个引用指向的对象是否为某一种类型
- 什么是多态:多种形态,多种状态,编译和运行有两种状态,编译期静态绑定,运行期动态绑定Animal a = new Cat();编译时编译器先判断a为Animal类,去Animal类中找eat()方法,结果找到了,编译通过,运行时,底层实际的对象是什么,就自动到该实际对象对应的eat()方法上,这就是多态的使用
- 软件开发原则之一:OCP原则,对扩展开放,对修改关闭
super关键字
- super能出现在实例方法和构造方法中
- super语法同this:
- super.属性名(访问父类的属性)
- super.方法名(实参)(访问父类的方法)
- super(实参)(调用父类的构造方法,比如子类构造方法中需要对父类的私有属性进行初始化赋值,则使用super(实参)把实参通过父类的构造方法赋给父类的私有属性)
- super不能用在静态方法中
- super大部分情况可以省略
- super()只能出现在构造方法第一行,通过当前构造方法调用父类的构造方法,目的是创建子类型对象时先初始化父类型特征
- 如果子类构造方法第一行,既没有this(),又没有super(),默认会有一个super(),表示通过当前子类的构造方法调用父类的无参数构造方法,所以必须保证父类的无参数构造方法存在
- 如果父类没有无参构造方法,则必须在子类写下构造方法,并在第一句写出带有参数的super(实参)方法
- 在构造方法执行过程中虽然不断在向上调用父类的构造方法,但是对象只创建了一个,super(实参)只是为了初始化父类的特征,而不是创建对象,super关键字代表的就是当前对象的那部分父类特征
- super.不能省的情况:
- 子类和父类有同名属性,且需要在子类中访问父类的该属性
- 子类覆盖了父类的方法,如果需要在子类中访问父类的方法,则需要super.方法名来访问父类的方法
- super不是引用,不保存内存地址,super也不指向任何对象,super只代表当前对象内部的那一块父类的特征,不能直接打印super
- super.不仅可以访问属性,也可以访问方法
Object中toString()方法
- toString方法作用是通过调用此方法将一个java对象转换成字符串的形式表示
- 打印引用默认调用toString()方法
- SUN公司开发java语言时建议所有子类都重写该方法
Object中的equals()方法
- “==”用于两基本数据类型时是判断二者值是否相同,用于引用数据类型时是判断是否为同一个对象,所以判断两个对象是否相等时不能用双等号
- Object的equals源码:
- equals方法默认使用的是双等号,两个值相同的对象会输出false,故需要重写equals方法
public boolean equals(Object obj) {
return (this == obj);
}
- 重写equals方法实例:
public Mytime {
private int year;
private int month;
private int day;
public Mytime() {}
public Mytime(int year, int month, int day) {
this.year = year;
this.month = month;
this.day = day;
}
public boolean equals(Object obj) {
if (obj == null || !(obj instanceof Mytime)) return false;
if (this == obj) return true;
Mytime t = (Mytime)obj;
return (this.year == t.year && this.month == t.month && this.day == t.day);
return false;
}
}
- equals()方法重新要彻底,把含有引用数据类型属性的obj类equals()方法全部重写
Object的finalize()方法
- finalize()方法只有一个方法体里面没有代码,这个方法是protected修饰的
- 这个方法不需要程序员手动调用,JVM的垃圾回收器负责调用这个方法,不像equals() toString()方法是需要写代码调用,finalize()方法只需要重写,重写完将来会有程序来调用
- finalize()方法的执行时机:当一个java对象即将被垃圾回收器回收的时候,垃圾回收器负责调用
- finalize()方法实际上是SUN公司为java程序员准备的一个时机,垃圾销毁时机,如果希望在对象销毁时机执行一段代码的话,这段代码要写到finalize()方法当中,类似于静态代码块实在类加载时刻执行,并且只执行了一次
- 给对象赋值null,可以把一个java对象变成垃圾
- java中的垃圾回收器不是轻易启动的,垃圾太少或者时间没到种种条件下有可能启动也可能不启动
- System.gc();该静态方法可以建议垃圾回收器启动,只是建议
Object的hashCode()方法
- 这个方法不是抽象方法,带有native关键字,底层调用C++代码
- 该方法返回的是哈希码,实际上是一个java对象的内存地址,经过哈希算法,得出的一个值,所以hashCode()方法的执行结果可以等同看做一个java对象的内存地址
String类
- String类的常用方法
- charAt(int index)返回指定索引的字符
- trim()删去字符串首尾的空白符
- replace(char old, char new)替换字符
- str.toLowerCase()字符串全部小写
- indexOf(String str)返回子串在字符串中首次出现的索引
- equals()判断两个字符串内容是否相同,string类重写了该方法
- toCharArray()把字符串转换为字符数组
- str1 == str2判断两个字符串地址是否相同
- str.isEmpty()判断字符串是空的
- str.contains(char c)判断字符串是否包括某个字符
- public String[] split()把字符串按某种规则分割
- startsWith(String pre)判断字符串是否以某子串开始
- endsWith()同上
- toString()返回字符串本身,因为String类重写了该方法
- String不能被继承,因为String类由final修饰,如果没有final修饰,string类就会有子类,修改string方法,修改字符串的值,违背初衷。
- String类被设计为不可变类原因:
- 字符串通常被用来储存敏感信息,如账号密码等,如果字符串可变就容易被修改,不安全
- 多线程当中只有不可变的值可对象是线程安全的,当一个线程修改了字符串的值,就会创新一个新的对象,不对其他线程的访问产生副作用
- 字符串不可变时,字符串常量池才有意义,他是为了减少同样值的字符串被反复创建,为运行时节省更多的堆内存,如果字符串可变,常量池失去意义,intern方法也没有用了,每次创建字符串时都开辟新的内存空间,占据更多内存。
- 字符串创建两种方式区别:
- String a = “abc”; 这种方式JVM会使用字符串常量池来管理字符串直接量,JVM先检查常量池中是否含有“abc”,若没有则将“abc”存入常量池,然后将其引用赋值给a
- String a = new String(“abc”); 执行时JVM会先将“abc”放入常量池中,然后再创建一个新的String对象,这个对象被保存在堆内存中,堆内存中对象的数据指向常量池中的字符串直接量
- 字符串拼接的几种方式:
- 通过 + :如果是拼接字符串直接量就用加号拼接,编译时编译器会直接优化为一个完整的字符串,和自己直接写一个完整的字符串是一样的,效率很高
- 通过StringBuilder和StringBuffer:如果字符串含有变量则可以通过二者来拼接,区别为StringBuilder不是线程安全的,另一个是
- 通过concat方法:如果是拼接两个字符串且含有变量,可以使用concat方法进行拼接
- 几种拼接方式底层原理:
- 通过加号拼接时如果字符串是变量,编译时编译器会默认采用StringBuilder对其进行优化,即自动创建StringBuilder实例并调用append方法进行拼接,但是如果拼接操作在循环当中则效率很低
- 通过StringBuilder和StringBuffer方法是使用字符串缓冲区,缓冲区的容量在创建对象时确定,并且默认为16,当拼接的字符串长度超过缓冲区容量时,会触发缓冲区的扩容机制,缓冲区扩容为2 * n + 2,频繁扩容会导致性能降低
字符串缓冲区扩容机制
字符串缓冲区扩容机制2 - concat方法是先创建一个足以容纳两个字符串的字节数组,然后将这两个字符串拼接到这个数组中,最后将其传化为字符串
- String类重写了toString()和equals()方法
- 比较两个字符串不能使用==,因为字符串是一个类,不是基本数据类型
- 字符串的toString()方法是返回字符串对象本身,equals()方法是判断两个字符串对象的值是否相同
StringBuilder和StringBuffer
- 二者共同点 :代表字符序列可变的字符串对象,他们有共同的父类AbstractStringBuilder,且两个类的构造方法和成员方法也基本相同。当一个StringBuilder对象被创建后,通过StringBuilder提供的append、insert、reverse、setCharAt、setLength等方法可以改变这个字符序列,一旦通过StringBuilder生成需要的字符序列后,可通过toString()方法将其转换为一个String对象。
- 不同点:StringBuffer是线程安全的,StringBuilder是非线程安全的,所以StringBuilder效率较高。
抽象类
- 把具有共同特征的类的共同特征提取出来,形成抽象类,抽象类本身是不存在的,所以抽象类是无法创建对象,也就是无法实例化的
- 抽象类属于引用数据类型
- 抽象类语法:public abstract class 类名{类体};
- 抽象类无法实例化,无法创建对象,所以抽象类是用来被子类继承的
- final和abstract不能联合使用,两个关键字是对立的
- 抽象类的子类可以是抽象类,也可以是非抽象类
- 抽象类虽然无法实例化,但是有构造方法,是提供给子类使用,比如子类的构造方法中默认super()
- 抽象方法是没有实现的方法,没有方法体,public abstract void doSome();分号结束,修饰符列表中有abstract关键字
- 抽象类中不一定有抽象方法,抽象方法只能出现在抽象类中
- 一个非抽象类继承抽象类,必须将抽象类中的抽象方法实现(对抽象方法的覆盖或者重写,叫做对抽象的实现)
- 没有方法体的不一定是抽象方法,Object类中就有很多方法都没有方法体,都是以分号结尾的,例如public native int hashCode();这个方法底层调用了C++写的动态链接库程序,前面修饰符列表中的native代表调用JVM程序
接口
基础语法
- 接口是一种引用数据类型,接口通常提取的都是行为动作(如计算,飞翔等)
- 接口是完全抽象的,抽象类是半抽象的,不能创建对象
- 接口定义语法:【修饰符列表】interface 接口名{};
- 接口支持多继承interface A extends A, B {}
- 接口中只包含两部分:
- 常量
- 抽象方法
interface MyMath {
public static final double PI = 3.1415926;
//接口中的常量的public static final可以省略
public abstract int sum(int a, int b);
//定义抽象方法时public abstract可以省略
}
- 接口中所有元素都是public修饰
- 接口抽象方法不能带有方法体
- 类和类之间叫继承,关键字用extends,类和接口之间叫实现,可以看做是继承,实现用关键字implements完成
- 当一个非抽象的类实现接口的话,必须将接口中所有的抽象方法全部实现(覆盖重写)
public class Test01 {
public static void main (String[] args) {
//多态
MyMath mm = new MyMathImpl();
int result1 = mm.sum(10, 20);
int result2 = mm.sub(20, 10);
System.out.println(result1 + result2);
}
}
interface MyMath {
double PI = 3.1415926;
int sum(int a, int b);
int sub(int a, int b);
}
class MyMathImpl implements MyMath {
//不能省略public,重写父类的方法不能权限更低,接口中都是public方法
public int sum(int a, int b) {
return a + b;
}
public int sub(int a, int b) {
return a - b;
}
}
- 集成和实现都存在:extends关键字在前,implements关键字在后
public class Test01 {
public static void main(String[] args) {
//多态 表面Animal没用
Flyable f = new Cat();
f.fly();
Flyable f2 = new Pig();
f2.fly();
Flyable f3 = new Fish();
f3.fly();
}
}
class Animal {
}
interface Flyable {
void fly();
}
class Cat extends Animal implements Flyable {
public void fly() {
System.out.println("飞猫起飞");
}
}
class Snake extends Animal {
}
//想飞翔就插翅膀这个接口
class Pig extends Animal implements Flyable {
public void fly() {
System.out.println("我是会飞的猪");
}
}
class Fish implements Flyable {
public void fly() {
System.out.println("我是飞鱼");
}
}
类实现接口可以多实现(多继承)
- 一个类可以同时实现多个接口,这种机制弥补了java中不能多继承的出现
- 一个接口如果需要调用另一个接口的方法,需要向下转型
- 接口和接口之间进行强制类型转换时,二者之前没有继承关系也可以强转,编译器没意见,但是运行时还是可能会出现ClassCastException(是SUN公司的骚操作,记住就行)
public static void main(String[] args) {
A a = new D();
B b = new D();
C c = new D();
/*
向下转型B和A没有继承关系也可以转,编译没问题,但是运行出错,记住就行
B b2 = (B)a;
b.m2();
*/
}
if (a instanceof D) {
D d = (D)a;
d.m2();
}
}
interface A {
void m1();
}
interface B {
void m2();
}
interface C {
void m3();
}
class D implements A, B, C {
//类实现接口需要重写接口中的抽象方法,public不能省略
public void m1(){}
public void m2(){}
public void m3(){}
}
接口在开发中的作用
- 接口在开发中的作用类似于多态在开发中的作用。多态:面向抽象编程,不要面向具体编程,降低程序耦合度,提高程序扩展力
pulbic class Master {
public void feed(Dog d) {}
public void feed(Cat c) {}
//如果需要其他宠物此时就需要再加一个方法(修改代码),扩展力太差,违背OCP原则:对扩展开放,对修改关闭
}
------------------------
public class Master {
public void feed (Animal a) {}
//面向Animal父类编程,父类比子类抽象,所以我们叫面向抽象编程,不要面向具体编程,提高程序扩展力
}
- 接口是完全抽象的,面向抽象编程可以改为面向接口编程,有了接口就有了插拔,可插拔表示扩展力很强而不是焊接死的,类似主板和内存条的关系
- 凡是可以使用has a描述的,同意以属性的方法存在(实例变量,属性)
public class Test {
public static void main(String[] args) {
//创建厨师对象,如果之后需要换厨师,只需要该test类中的程序,其他不需要改,因为customer中属性使用的是接口FoodMenu
FoodMenu cooker1 = new ChinaCooker();
//创建顾客对象
Customer customer = new Customer(cooker1); //传入cooker1防止出现空指针异常
//顾客点菜
customer.order();
}
}
//顾客类,顾客has a FoodMenu
public class Customer{
private FoodMenu foodMenu; //封装好习惯
public Customer() {}
public Customer(FoodMenu fooMenu) {
this.foodMenu = foodMenu;
}
// setter getter
public void setFoodMenu(FoodMenu foodMenu) {
this.foodMenu = foodMenu;
}
public FoodMenu getFoodMenu() {
return foodMenu;
}
public void order() {
FoodMenu fm = this.getFoodMenu();
fm.shizichaodan();
fm.mayishangshu();
}
}
public interface FoodMenu {
void shizichaodan(){}
void mayishangshu(){}
}
//中餐厨师,实现餐单上的菜,厨师是接口的实现者
public class ChinaCooker implements FoodMenu{
public void shizichaodan() {
Sytstem.out.println("中餐师傅做的柿子炒蛋");
}
public void shizichaodan() {
Sytstem.out.println("中餐师傅做的蚂蚁上树");
}
}
public class AmercaCooker implements FoodMenu{
public void shizichaodan() {
Sytstem.out.println("西餐师傅做的柿子炒蛋");
}
public void mayishangshu() {
Sytstem.out.println("西餐师傅做的蚂蚁上树");
}
}
- 总结一句话:面向接口编程,可以降低程序耦合度,提高程序扩展力,符合OCP开发原则。接口的使用离不开多态机制(接口 + 多态 可以实现解耦合)。任何一个接口都有调用者和实现者,接口· 满足like a关系的,表示实现关系,比如 cooker like a menu,类实现接口
抽象类和接口的关系
- 不同点:
- 抽象类是半抽象,接口是完全抽象
- 抽象类只能有抽象方法、静态方法、默认方法和私有方法;抽象类则可以有普通方法
- 抽象类可以有普通成员变量也可以有静态常量;接口则只能有静态常量
- 抽象类中有构造方法,它的构造方法不是为了创建对象,而是为了子类调用这些构造器来完成属于抽象类的初始化操作;接口是一种规范,其中没有构造方法和初始化块java初始化块
- 一个类最多有一个直接父类,包括抽象类;一个类可以实现多个接口弥补java单继承的不足
- 共同点:
- 二者都不能被实例化,位于继承树的顶端,被其他类实现和继承
- 都可以有抽象方法,实现接口或继承抽象类的普通子类必须实现这些抽象方法
集合
集合概述
- 集合实际上是一个容器,可以来容纳其他类型的数据,数组就是一种集合
- 在实际开发中,假设连接数据库,数据库当中有十条记录,那么假设把这十条记录查询出来,在java程序中会将十条数据封装成十个java对象,然后将这些对象放到某一个集合当中,把集合传到前端,然后遍历集合,将一个一个数据展现出来
- 集合不能直接存储基本数据类型,另外集合也不能直接存储java对象,集合当中存储的都是java对象的内存地址,或者说集合中存储的是引用,
list.add(100); // 自动装箱Integer
- 注意集合在java中本身是一个容器,是一个对象,集合中任何时候存储的都是“引用”
- 在java中不同集合底层会对应不同的数据结构,往不同集合中存储元素,等于将数据放到了不同的数据结构当中。数据结构就是数据存储结构,不同数据结构,数据存储的方式不同,比如数组、二叉树、链表、哈希表…
- 集合在java JDK下的java.util.*下,所有集合类和集合接口都在java.util包下
集合继承结构
- 集合分为两大类:
- 单个方式存储元素,这一类集合中超级父接口为:java.util.Collection;
- 键值对的方式存储元素,这一类集合中超级父接口为:java.util.Map;
Collection集合结构
Map集合继承结构
总结
- ArrayList:底层是数组
- LinkedList:底层是双向链表
- Vector:底层是数组,线程安全的,效率较低,使用较少
- HashSet:底层是HashMap,放到HashSet集合中的元素等同于放到HashMap集合key部分了
- TreeSet:底层是TreeMap,放到了TreeSet集合中的元素等同于放到了TreeMap的key部分
- HashMap:底层是哈希表
- HashTable:底层也是哈希表,只不过线程安全的,效率较低,使用较少
- Properties:是线程安全的,key和value只能存储String类型
- TreeMap底层是二叉树,TreeMap集合的key可以自动按照大小顺序排序
- List集合存储元素的特点:有序可重复
- Set集合存储元素的特点:无序不可重复
- SortedSet集合储存元素特点:首先是无序不可重复,但是集合中的元素可排序
- Map集合的key就是Set集合,在Set集合中放数据,实际上放到了Map集合的key部分
Collection集合常用方法
- 不使用泛型,可以存储Object的所有子类型,使用泛型之后,Collection中只能存储某个具体的类型。Collection中什么都能存,只要是Object的子类型就行,集合中不能存储基本数据类型,也不能存储java对象,只是存储java对象的内存地址
boolean add(Object a);
int size();
void clear();
boolean contains(Object a);
boolean remove(Object a);
boolean isEmpty();
Object[] toArray(); // 调用这个方法把集合转成数组
Collection集合迭代
- 以下方法在Map集合中不可用
- 迭代三步走:
- 获取集合对象的迭代器对象Iterator
Iterator it = c.iterator();
- 通过以上获取的迭代器对象开始迭代。迭代器对象Iterator中有两个方法:(1)
boolean hasNext() // 如果仍有元素可以迭代,则返回true
(2)Object next() // 返回迭代器的下一个元素
- 获取集合对象的迭代器对象Iterator
泛型
泛型概述
- 泛型这种语法机制只在编译阶段起作用,是给编译器参考的,运行阶段没用
- 使用泛型List之后,表示List集合中只能存储Animal类型的数据
- 用泛型来指定集合中存储的数据类型
- 使用泛型之后,集合中的元素数据更加统一了
- 泛型好处:
- 集合中元素类型更统一
- 从集合中取出的元素类型是泛型指定的类型,不需要进行大量的向下转型
public class GenericTest01 {
public static void main(String[] args) {
List<Animal> mylist = new ArrayList<Animal>();
Cat c = new Cat();
Bird b = new Bird();
mylist.add(c);
mylist.add(b);
// 加上泛型表示迭代器迭代的是Animal类型
Iterator<Animal> it = mylist.iterator();
while (it.hasNext()) {
// 使用泛型之后,每次迭代返回的数据都是Animal类型
Animal a = it.next();
if (a instanceof Cat) {
Cat c2 = (Cat) a;
c2.catchMouse();;
}
a.move();
}
}
}
class Animal {
public void move() {
System.out.println("move!");
}
}
class Cat extends Animal {
public void catchMouse() {
System.out.println("catch mouse!");
}
}
class Bird extends Animal {
public void fly() {
System.out.println("flying!");
}
}
类型自动推断
- JDK8之后推出自动类型推断机制(又称为钻石表达式)
public class GenericTest02 {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("http://www.baidu.com");
list.add("http://www.jingdong.com");
list.add("http://www.taobao.com");
Iterator<String> it = list.iterator();
while (it.hasNext()) {
String s = it.next();
String newString = s.substring(7);
System.out.println(newString);
}
}
}
自定义泛型
- <>尖括号中的St为标识符,随便写就行
- 不使用泛型则默认返回的是Object类型
public class GenericTest03<St> {
public void doSome(St s) {
System.out.println(s);
}
public static void main(String[] args) {
GenericTest03<String> s = new GenericTest03<>();
s.doSome("abs");
}
}
反射
反射机制概述
- 通过hava语言中的反射机制可以直接操作字节码文件,即class文件
- 反射机制的相关类在java.lang.reflect.*
- 反射机制中的类:
- java.lang.Class 代表整个字节码
- java.lang.reflect.Method 代表字节码中的方法字节码
- java.lang.reflect.Constructor 代表字节马当中的构造方法字节码
- java.lang.reflect.Field 代表字节码中的属性字节码
- 字节码文件装载到JVM中的时候只装载一份
获取java.lang.Class实例三种方法
Class.forName(完整包名字符串);
- 该方法为静态方法
- 方法的参数是一个字符串
- 字符串需要的是一个完整类名
- 完整类名必须带有报名,java.lang包也不能省略
- 任何一个类的
getClass()
方法 - java语言中任何一种类型包括基本数据类型都有.class属性
public class ReflectTest01 {
public static void main(String[] args) {
// c1代表String.class文件,或者说c1代表String类型
Class c1 = null;
try {
c1 = Class.forName("java.lang.String");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
String s = "abc";
Class c2 = s.getClass();
System.out.println(c1 == c2);
Class c3 = String.class;
System.out.println(c2 == c3);
}
}
通过反射实例化对象
- newInstance()方法会调用类的无参数构造方法,完成对象的创建,必须保证无参数构造是存在的
public class ReflectTest02 {
public static void main(String[] args) {
User s1 = new User();
System.out.println(s1);
Class s2 = null;
Object c = null;
try {
s2 = Class.forName("bean.User");
c = s2.newInstance();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} finally {
System.out.println(c);
}
}
}
通过读属性文件实例化对象
- java代码写一遍,在不改变java源代码的基础上,可以做到对不同对象的实例化,非常灵活,符合OCP原则
public class ReflectTest03 {
public static void main(String[] args) throws Exception {
// 通过IO流读取ClassInfo.properties文件
FileReader reader = new FileReader("D:/Project/src/ClassInfo.properties");
// 创建属性类对象Map
Properties pro = new Properties(); // key和value都是String
// 加载
pro.load(reader);
// 关闭流
reader.close();
// 通过key获取value
String className = pro.getProperty("className");
System.out.println(className);
//通过反射机制创建对象
Class c = Class.forName(className);
Object o = c.newInstance();
System.out.println(o);
}
}
使用forName()方法执行静态代码块
- 这个方法的执行会导致类加载,如果希望一个类的静态代码块执行,其他代码一律不执行,可以使用Class.forName(“完整类名”),类加载时,静态代码块执行
public class ReflectTest04 {
public static void main(String[] args) {
try {
Class c = Class.forName("reflect.myClass");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
class myClass {
static {
// 静态代码块在类加载的时候执行,并且只执行一次
System.out.println("静态代码块执行!");
}
}
获取类途径下文件的绝对路径
- 之前的路径缺点:移植性差,在IDEA中默认当前路径是project的根,离开了IDEA就没有用了
- 以下为一种通用方式,即使换代码位置也能通用
- 前提是文件必须在类路径下,即src下
- src是类的根路径
Thread.currentThread().getContextClassLoader().getResource("").getPath();
public class AboutPath {
public static void main(String[] args) throws Exception {
// Thread.currentThread() 当前线程对象
// getContextClassLoader() 是线程对象的方法,可以获取当前线程的类加载器对象
// getResource("") 是类加载器对象的方法,默认从类的根路径下加载资源
String path = Thread.currentThread().getContextClassLoader().getResource("ClassInfo.properties").getPath();
System.out.println(path);
String path2 = Thread.currentThread().getContextClassLoader().getResource("reflect/ReflectTest01.class").getPath();
System.out.println(path2);
}
}
异常
- java程序出现不正常情况,这种情况称为异,java把异常信息打印在控制台上,供程序员参考。异常的作用是增强程序健壮性
- java中异常以类的形式存在,每一个异常类都可以创建异常对象
- 异常的继承结构:
- Object
- throwable
- 2下有error和exception两个子类,不管是错误还是异常都是可以抛出的,所有错误只要发生,Java程序只有一个结果就是中止程序的执行,推出JVM,错误是不能处理的。
- (1)exception直接子类
(2)RuntimeException
分别称为编译时异常和运行时异常,常见的RuntimeException有ClassCastException、NullPointerException等等
- 编译时异常并不是在编译阶段发生的异常,二是表示必须在编写程序的时候预先对这种异常进行处理,如果不处理编译器会报错,编译时异常又称为受控异常或受检异常(CheckedException);运行时异常在编写程序阶段可以处理也可以不处理。
- 所有异常都发生在运行阶段
- java异常处理两种方式:
- 在方法声明的位置上使用throws关键字抛给上一级,如果异常一直上抛到main方法,main方法抛给JVM,则程序终止
- 使用try…catch语句进行异常的捕捉
- 下面代码中new文件流时源码抛出FileNotFoundException,该异常为编译时异常,所以需要预处理,要么在m1后使用throws抛给main方法,要么在m1中使用异常捕捉,代码中是抛给main方法,main方法中使用异常捕捉,一般不建议在main方法中使用throws关键字上抛,建议进行一场捕捉,增强程序健壮性
public class ExceptionTest {
public static void main(String[] args) {
try {
m1();
} catch (ClassNotFoundEception e) {
System.out.println("文件删除或不存在!");
}
}
public static void m1 throws FileNotFoundException() {
new FileInputStr
}
}
- 只要异常没有捕捉采用上报的方式,则此方法后续的代码不会执行,try语句块中某一行出现异常,该行后面的代码不会执行,try…catch捕捉异常之后,后续代码可以执行。
- 一个方法方法体中异常上报的话,方法就结束了
- catch后面小括号中的异常类型可以是具体的,也可以是父类,比如Exception,catch写多个的时候,从上到下必须遵循从小到大
- JDK8新特性:catch后小括号可以用“||”来同时写多种异常
- 异常两种方法:
- getMessage()获取异常的简单描述信息
- printStackTrace()打印异常堆栈信息,java后台打印时采用了异步线程的方式,异常信息追踪信息,从上往下一行一行看,SUN写的代码不用看,问题出在自己编写的代码中
finally语句
- finally子句中的代码是最后执行的,并且是一定会执行的,即使try语句块中的代码出现了异常
- finally子句必须和try一起出现,不能单独编写
- try中局部变量finally中无法使用,应该声明在try外面
public class ExceptionTest01 {
public static void main(String[] args) {
FileInputStream fis = null;
try {
fis = new FileInputStream("D://XXX");
}catch (FileNotFoundException e) {
e.printStackTrace();
}finally {
if (fis != null) {
try {
fis.close();
}catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
- try和finally可以连用,没有catch
- 在try中使用System.exit()退出JVM虚拟机的话,finally语句就不执行了
异常经典题
public class ExceptionTest01 {
public static void main(String[] args) {
int result = m();
System.out.println(result);
}
public static void m() {
int i = 100;
try {
return i;
}finally {
i++;
}
}
}
final finally finalize区别
- final关键字:
- final修饰的类无法继承
- final修饰的方法无法覆盖
- final修饰的变量不能重新赋值
- finally关键字
- 和try一起联用
- finally语句块中的代码是必须执行的
- finalize标识符:是一个Object类中的方法名,这个方法是由垃圾回收器调用的
自定义异常
- 编写一个类继承Exception或者RuntimeException
- 编写两个构造方法,一个有参数,一个无参数