目录
9.7 JDK 的元 Annotation(元注解, 了解)
9.7.2 元注解的种类 (使用不多,了解, 不用深入研究)
10.6.2 try-catch-finally 执行顺序小结
11.5.3 String和StringBuffer相互转换
11.6.3 String、StringBuffer 和 StringBuilder 的比较
11.6.4 String、StringBuffer 和 StringBuilder 的选择
11.10.1 BigInteger和BigDecimal介绍
12.5.2 ArrayList的底层操作机制源码分析(重点,难点.)
12.8 ArrayList 和 LinkedList 比较
12.8.1 ArrayList 和 LinkedList 的比较
12.12.2 Hashtable 和 HashMap 对比
类变量也叫静态变量/静态属性,是该类的所有对象共享的变量,任何一个该类的对象去访问它时,取到的都是相同的值,同样任何一个该类的对象去修改它时,修改的也是同一个变量。这个从前面的图也可看出来。
定义语法:
访问修饰符static 数据类型变量名;
static 访问修饰符数据类型变量名;
类名.类变量名
或者 对象名.类变量名【静态变量的访问修饰符的访问权限和范围和普通属性是一样的。】
推荐使用: 类名.类变量名;
1.什么时候需要用类变量
当我们需要让某个类的所有对象都共享一个变量时,就可以考虑使用类变量(静态变量):比如:定义学生类,统计所有学生共交多少钱。
2.类变量与实例变量(普通属性)区别
类变量是该类的所有对象共享的,而实例变量是每个对象独享的。
3.加上static称为类变量或静态变量,否则称为实例变量/普通变量/非静态变量
4.类变量可以通过 类名.类变量名 或者 对象名.类变量名 来访问,但java设计者推荐我们使用 类名.类变量名方式访问。【前提是满足访问修饰符的访问权限和范围】
5.实例变量不能通过 类名.类变量名 方式访问。
6.类变量是在类加载时就初始化了,也就是说,即使你没有创建对象,只要类加载了,就可以使用类变量了。
7.类变量的生命周期是随类的加载开始,随着类消亡而销毁。
类方法也叫静态方法。
形式如下:
访问修饰符 static 数据返回类型方法名 (){ }
static 访问修饰符 数据返回类型方法名 (){ }
使用方式: 类名.类方法名 或者 对象名.类方法名
1)类方法和普通方法都是随着类的加载而加载,将结构信息存储在方法区:
类方法中无this的参数
普通方法中隐含着this的参数
2)类方法可以通过类名调用,也可以通过对象名调用。
3)普通方法和对象有关,需要通过对象名调用,比如 对象名.方法名(参数),不能通过类名调
用。
4)类方法中不允许使用和对象有关的关键字,比如this和super。普通方法(成员方法)可以。
5)类方法(静态方法)中 只能访问静态变量或静态方法。
6)普通成员方法,既可以访问非静态成员,也可以访问静态成员。
小结: 静态方法,只能访问静态的成员,非静态的方法,可以访问静态成员和非静态成员(必须遵守访问权限)
代码:
- packagecom.hspedu.static_;
- publicclassStaticMethodDetail{
- publicstaticvoidmain(String[]args){
- D.hi();//ok
- //非静态方法,不能通过类名调用
- //D.say();,错误,需要先创建对象,再调用
- newD().say();//可以
- }
- }
- class D {
- private int n1 = 100;
- private static int n2 = 200;
- public void say() {//非静态方法,普通方法
- }
- public static void hi() {//静态方法,类方法
- //类方法中不允许使用和对象有关的关键字,
- //比如this 和 super。普通方法(成员方法)可以。
- //System.out.println(this.n1);
- }
- //类方法(静态方法)中 只能访问 静态变量 或静态方法
- //口诀:静态方法只能访问静态成员.
- public static void hello() {
- System.out.println(n2);
- System.out.println(D.n2);
- //System.out.println(this.n2);不能使用
- hi();//OK
- //say();//错误
- }
- //普通成员方法,既可以访问 非静态成员,也可以访问静态成员
- //小结: 非静态方法可以访问 静态成员和非静态成员
- public void ok() {
- //非静态成员
- System.out.println(n1);
- say();
- //静态成员
- System.out.println(n2);
- hello();
- }
- }
解释main方法的形式:public static void main(String[ ] args){ }
1. main方法时虚拟机调用
2. java虚拟机需要调用类的main()方法,所以该方法的访问权限必须是public
3. java虚拟机在执行main()方法时不必创建对象,所以该方法必须是static
4.该方法接收String类型的数组参数,该数组中保存执行java命令时传递给所运行的类的参数
5. java 执行的程序 参数1 参数2 参数3
1)在main()方法中,我们可以直接调用main方法所在类的静态方法或静态属性。
2)但是,不能直接访问该类中的非静态成员,必须创建该类的一个实例对象后,才能通过这个对象去访问类中的非静态成员
代码化块又称为初始化块,属于类中的成员[即是类的一部分],类似于方法,将逻辑语句封装在方法体中,通过{ }包围起来。
但和方法不同,没有方法名,没有返回,没有参数,只有方法体,而且不用通过对象或类显式调用,而是加载类时,或创建对象时隐式调用。
[修饰符] {
代码
};
说明注意:
1)修饰符可选,要写的话,也只能写static
2)代码块分为两类,使用static修饰的叫静态代码块,没有static修饰的,叫普通代码块/非静态代码块。
3)逻辑语句可以为任何逻辑语句(输入、输出、方法调用、循环、判断等)
4) ;号可以写上,也可以省略。
1)相当于另外一种形式的构造器(对构造器的补充机制),可以做初始化的操作
2)场景:如果多个构造器中都有重复的语句,可以抽取到初始化块中,提高代码的重用性
1) static代码块也叫静态代码块,作用就是对类进行初始化,而且它随着类的加载而执行,并且只会执行一次。如果是普通代码块,每创建一个对象,就执行。
2) 类什么时候被加载
1.创建对象实例时(new)
2.创建子类对象实例,父类也会被加载
3.使用类的静态成员时(静态属性,静态方法) 案例演示:A类extends B类的静态块
3) 普通的代码块,在创建对象实例时,会被隐式的调用。
被创建一次,就会调用一次。
如果只是使用类的静态成员时,普通代码块并不会执行。
小结: 1.static代码块是类加载时,执行,只会执行一次
2.普通代码块是在创建对象时调用的,创建一次,调用一次
3.类加载的3种情况,需要记住.
4) 创建一个对象时,在一个类调用顺序是:(重点,难点):
1.调用静态代码块和静态属性初始化(注意:静态代码块和静态属性初始化调用的优先级一样,如果有多个静态代码块和多个静态变量初始化,则按他们定义的顺序调用)
2.调用普通代码块和普通属性的初始化(注意:普通代码块和普通属性初始化调用的优先级一样,如果有多个普通代码块和多个普通属性初始化,则按定义顺序调用)
3.调用构造方法。
5) 构造器的最前面其实隐含了super()和调用普通代码块,静态相关的代码块,属性初始化,在类加载时,就执行完毕,因此是优先于构造器和普通代码块执行
- class A{
- public A(){ //构造器
- //这里有隐藏的执行要求
- //(1) superO://这个知识点,在前面讲解继承的时候,
- //(2)调用普通代码块的
- System.out.println("ok");
- }}
6)我们看一下创建一个子类对象时(继承关系),他们的静态代码块,静态属性初始化,普通代码块,普通属性初始化,构造方法的调用顺序如下:
1. 父类的静态代码块和静态属性(优先级一样,按定义顺序执行)
2. 子类的静态代码块和静态属性(优先级一样,按定义顺序执行)
3. 父类的普通代码块和普通属性初始化(优先级一样,按定义顺序执行)
4. 父类的构造方法
5. 子类的普通代码块和普通属性初始化(优先级一样,按定义顺序执行)
6. 子类的构造方法 //面试题
7)静态代码块只能直接调用静态成员(静态属性和静态方法),普通代码块可以调用任意成员。学习比较麻烦,工作轻松
单例(单个的实例)
1. 所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法
2. 单例模式有两种方式: 1)饿汉式 2)懒汉式
步骤如下:
1)构造器私有化 =》防止直接new
2)类的内部创建对象
3)向外暴露一个静态的公共方法。getInstance
代码:
- package com.hspedu.single_;
- public class SingleTon01 {
- public static void main(String[] args) {
- GirlFriend xh = new GirlFriend("小红");
- GirlFriend xb = new GirlFriend("小白");
- //通过方法可以获取对象
- GirlFriend instance = GirlFriend.getInstance();
- System.out.println(instance);
- GirlFriend instance2 = GirlFriend.getInstance();
- System.out.println(instance2);
- System.out.println(instance == instance2);//T
- //System.out.println(GirlFriend.n1);
- //...
- }}
- //有一个类, GirlFriend
- //只能有一个女朋友
- class GirlFriend {
- private String name;
- //public static int n1 = 100;
- //为了能够在静态方法中,返回 gf对象,需要将其修饰为static
- //對象,通常是重量級的對象, 餓漢式可能造成創建了對象,但是沒有使用.
- private static GirlFriend gf = new GirlFriend("小红红");
- //如何保障我们只能创建一个 GirlFriend 对象
- //步骤[单例模式-饿汉式]
- //1. 将构造器私有化
- //2. 在类的内部直接创建对象(该对象是static)
- //3. 提供一个公共的static方法,返回 gf对象
- private GirlFriend(String name) {
- System.out.println("構造器被調用.");
- this.name = name;
- }
- public static GirlFriend getInstance() {
- return gf;
- }
- @Override
- public String toString() {
- return "GirlFriend{" +
- "name='" + name + '\' +
- '}';
- }}
- package com.hspedu.single_;
- /**
- * 演示懶漢式的單例模式
- */
- public class SingleTon02 {
- public static void main(String[] args) {
- //new Cat("大黃");
- //System.out.println(Cat.n1);
- Cat instance = Cat.getInstance();
- System.out.println(instance);
- //再次調用getInstance
- Cat instance2 = Cat.getInstance();
- System.out.println(instance2);
- System.out.println(instance == instance2);//T
- }
- }
- //希望在程序運行過程中,只能創建一個Cat對象
- //使用單例模式
- class Cat {
- private String name;
- public static int n1 = 999;
- private static Cat cat ; //默認是 null
- //步驟
- //1.仍然構造器私有化
- //2.定義一個static 靜態屬性對象
- //3.提供一個public 的 static 方法,可以返回一個Cat對象
- //4.懶漢式,只有當用戶使用getInstance 時,才返回cat對象, 後面再次調用時,會返回上次創建的cat對象
- //
- 從而保證了單例
- private Cat(String name) {
- System.out.println("構造器調用...");
- this.name = name;
- }
- public static Cat getInstance() {
- if(cat == null) {//如果還沒有創建 cat 對象
- cat = new Cat("小可愛");
- }
- return cat;
- }
- @Override
- public String toString() {
- return "Cat{" +
- "name='" + name + '\' +
- '}';
- }}
1. 二者最主要的区别在于创建对象的时机不同:饿汉式是在类加载就创建了对象实例,
而懒汉式是在使用时才创建。
2. 饿汉式不存在线程安全问题,懒汉式存在线程安全问题。(后面学习线程,会完善一把)
3. 饿汉式存在浪费资源的可能。因为如果程序员一个对象实例都没有使用,那么饿汉式创建的对象就浪费了,懒汉式是使用时才创建,就不存在这个问题。
4. 在我们javaSE标准类中,java.lang.Runtime就是经典的单例模式。
final中文意思:最后的,最终的.
final可以修饰类、属性、方法和局部变量.
在某些情况下,程序员可能有以下需求,就会使用到final:
1) 当不希望类被继承时,可以用final修饰.
2) 当不希望父类的某个方法被子类猎盖/重写(override)时,可以用final关键字修饰。
3) 当不希望类的的某个属性的值被修改,可以用final修饰.
4) 当不希望某个局部变量被修改,可以使用final修饰
代码:
- //如果我们要求A类不能被其他类继承
- //可以使用final 修饰 A类
- final class A { }
- class C {
- //如果我们要求hi不能被子类重写
- //可以使用final 修饰 hi方法
- public final void hi() {}
- }
- //当不希望类的的某个属性的值被修改,可以用final修饰
- class E {
- public final double TAX_RATE = 0.08;//常量
- //当不希望某个局部变量被修改,可以使用final修饰
- class F {
- public void cry() {
- //这时,NUM 也称为 局部常量
- final double NUM = 0.01;
- //NUM = 0.9;
- System.out.println("NUM=" + NUM);
1 final修饰的属性又叫常量,一般用 XX XX XX来命名
2) final修饰的属性在定义时,必须赋初值,并且以后不能再修改,赋值可以在如下位置之一
1. 定义时:如public final double TAX_RATE=0.08;
2. 在构造器中
3. 在代码块中。
3)如果final修饰的属性是静态的,则初始化的位置只能是
1. 定义时 2. 在静态代码块不能在构造器中赋值。
4) final类不能继承,但是可以实例化对象。
5) 如果类不是final类,但是含有final方法,则该方法虽然不能重写,但是可以被继承。
代码:
- class AA{
- /*
- 1. 定义时:如 publicfinal doubleTAX_RATE=0.08;
- 2. 在构造器中
- 3. 在代码块中
- */
- public final double TAX_RATE = 0.08;//1.定义时赋值
- public final double TAX_RATE2 ;
- public final double TAX_RATE3 ;
- public AA() {//构造器中赋值
- TAX_RATE2= 1.1;
- }
- {//在代码块赋值
- TAX_RATE3= 8.8;
- }
- /*
- 如果final 修饰的属性是静态的,则初始化的位置只能是
- 1 定义时 2 在静态代码块 不能在构造器中赋值。
- */
- public static final double TAX_RATE = 99.9;
- public static final double TAX_RATE2 ;
- static {
- TAX_RATE2= 3.3;
- }
- }
- //final 类不能继承,但是可以实例化对象
- final class CC { }
- //即,仍然遵守继承的机制.
- class DD {
- public final void cal() {
- //如果类不是final 类,但是含有final方法,则该方法虽然不能重写,但是可以被继承
- System.out.println("cal()方法");
- }
5)一般来说,如果一个类已经是final类了,就没有必要再将方法修饰成final方法。
6) final不能修饰构造方法(即构造器)
7) final和static往往搭配使用,效率更高,不会导致类加载.底层编译器做了优化处理。
- class Demo{
- public static final int i=16; //
- static{
- System.out.println(“学习~");
- }}
8)包装类(Integer,Double,Float,Boolean等都是final),String也是final类。
当父类的一些方法不能确定时,可以用abstract关键字来修饰该方法,这个方法就是抽象方法,用abstract来修饰该类就是抽象类。
我们看看如何把Animal做成抽象类,并让子类Cat类实现。
- abstract class Animal{
- String name;
- int age;
- abstract public void cry();
- }
1) 用abstract关键字来修饰一个类时,这个类就叫抽象类
访问修饰符 abstract 类名 {
}
2) 用abstract关键字来修饰一个方法时,这个方法就是抽象方法
访问修饰符 abstract 返回类型 方法名 (参数列表); //没有方法体
3) 抽象类的价值更多作用是在于设计,是设计者设计好后,让子类继承并实现抽象类()
4) 抽象类, 是考官比较爱问的知识点,在框架和设计模式使用较多
1) 抽象类不能被实例化
2) 抽象类不一定要包含abstract方法。也就是说,抽象类可以没有abstract方法
3) 一旦类包含了abstract方法,则这个类必须声明为abstract
4) abstract 只能修饰类和方法,不能修饰属性和其它的。
5) 抽象类可以有任意成员【抽象类本质还是类】,比如: 非抽象方法、构造器、静态属性等等
6) 抽象方法不能有主体,即不能实现.如下所示
abstract void aaa(){}; 是错误的!!!
7) 如果一个类继承了抽象类,则它必须实现抽象类的所有抽象方法,
除非它自己也声明为abstract类。
8) 抽象方法不能使用private、final和static来修饰,因为这些关键字都是和重写相违背的。
接口就是给出一些没有实现的方法,封装到一起,到某个类要使用的时候,在根据具体情况把这些方法写出来。语法:
interface 接口名 {
//属性
//抽象方法
}
class 类名 implements 接口 {
自己属性;
自己方法;
必须实现的接口的抽象方法
}
小结:接口是更加抽象的抽象的类,抽象类里的方法可以有方法体,接口里的所有方法都没有方法体【jdk7.0】。接口体现了程序设计的多态和高内聚低偶合的设计思想。
特别说明:Jdk8.0后 接口类可以有静态方法,默认方法,也就是说接口中可以有方法的具体实现
1) 接口不能被实例化
2) 接口中所有的方法是 public 方法,接口中抽象方法,可以不用 abstract 修饰:
void aaa();
实际上是 abstract void aa();
3) 一个普通类实现接口,就必须将该接口的所有方法都实现。
4) 抽象类实现接口,可以不用实现接口的方法。
5) 一个类同时可以实现多个接口
6) 接口中的属性,只能是 final 的,而且是 public static final修饰符。
比如:int a=1;实际上是 public static final int a=1;(必须初始化)
7) 接口中属性的访问形式:接口名.属性名
8) 接口不能继承其它的类,但是可以继承多个别的接口
interface A extends B,C{ }
9) 接口的修饰符只能是 public和默认,这点和类的修饰符是一样的。
接口和继承解决的问题不同
继承的价值主要在于: 解决代码的复用性和可维护性。
接口的价值主要在于: 设计,设计好各种规范(方法),让其它类去实现这些方法。即更加的灵活..
接口比继承更加灵活
接口比继承更加灵活,继承是满足is- a的关系,而接口只需满足like - a的关系。
接口在一定程度 上实现代码解耦[即:接口规范性+动态绑定机制]
如果定义类在局部位置(方法中/代码块):(1) 局部内部类 (2) 匿名内部类
定义在成员位置 (1) 成员内部类 (2) 静态内部类
一个类的内部又完整的嵌套了另一个类结构。被嵌套的类称为内部类(inner class),嵌套其他类的类称为外部类(outer class)。是我们类的第五大成员【思考:类的五大成员是哪些?[属性、方法、构造器、代码块、内部类]】,内部类最大的特点就是可以直接访问私有属性,并且可以体现类与类之间的包含关系,注意:内部类是学习的难点,同时也是重点,后面看底层源码时,有大量的内部类.
- class Outer{//外部类
- class Inner{
- //内部类
- }}
- class Other{//外部其他类
- }
定义在外部类局部位置上(比如方法内):
1)局部内部类((有类名)
2)匿名内部类(没有类名,重点!!!!!!!!)
定义在外部类的成员位置上:
1)成员内部类(没用static修饰)
2)静态内部类(使用static修饰)
说明:局部内部类是定义在外部类的局部位置,比如方法中,并且有类名。
1.可以直接访问外部类的所有成员,包含私有的
2.不能添加访问修饰符,因为它的地位就是一个局部变量。局部变量是不能使用
修饰符的。但是可以使用final修饰,因为局部变量也可以使用final
3.作用域:仅仅在定义它的方法或代码块中。
4.局部内部类 ---访问---->外部类的成员[访问方式:直接访问]
5.外部类---访问---->局部内部类的成员
访问方式:创建对象,再访问(注意:必须在作用域内)
记住:(1)局部内部类定义在方法中/代码块
(2)作用域在方法体或者代码块中
(3)本质仍然是一个类
6.外部其他类---不能访问----->局部内部类(因为局部内部类地位是一个局部变量)
7.如果外部类和局部内部类的成员重名时,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问
System.out.println("外部类的n2=”+外部类名.this.n2);
(1)本质是类 (2)内部类 (3)该类没有名字 (4)同时还是一个对象
说明:匿名内部类是定义在外部类的局部位置,比如方法中,并且没有类名
1. 匿名内部类的基本语法
new 类或接口 (参数列表){
类体
};
2. 匿名内部类的语法比较奇特,请大家注意,因为匿名内部类既是一个类的定义同时它本身也是一个对象,因此从语法上看,它既有定义类的特征,也有创建对象的特征,对前面代码分析可以看出这个特点,因此可以调用匿名内部类方法。
3. 可以直接访问外部类的所有成员,包含私有的
4. 不能添加访问修饰符,因为它的地位就是一个局部变量。
5. 作用域:仅仅在定义它的方法或代码块中
6. 匿名内部类---访问---->外部类成员[访问方式:直接访问]
7. 外部其他类---不能访问----->匿名内部类(因为匿名内部类地位是一个局部变量)
8. 如果外部类和匿名内部类的成员重名时,匿名内部类访问的话,默认遵循就近原则如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问
代码:
- package com.hspedu.innerclass;
- public class AnonymousInnerClassDetail {
- public static void main(String[] args) {
- Outer05 outer05 = new Outer05();
- outer05.f1();
- //外部其他类---不能访问----->匿名内部类
- System.out.println("main outer05 hashcode=" + outer05);
- }
- }
- class Outer05 {
- private int n1 = 99;
- public void f1() {
- //创建一个基于类的匿名内部类
- //不能添加访问修饰符,因为它的地位就是一个局部变量
- //作用域 : 仅仅在定义它的方法或代码块中
- Person p = new Person(){
- private int n1 = 88;
- @Override
- public void hi() {
- //可以直接访问外部类的所有成员,包含私有的
- //如果外部类和匿名内部类的成员重名时,匿名内部类访问的话,
- //默认遵循就近原则,如果想访问外部类的成员,则可以使用 (外部类名.this.成员)去访问
- System.out.println("匿名内部类重写了 hi 方法 n1=" +n1 +
- " 外部内的n1="+Outer05.this.n1 );
- //Outer05.this 就是调用 f1 的 对象
- System.out.println("Outer05.this hashcode=" + Outer05.this);
- }
- };
- p.hi();//动态绑定, 运行类型是 Outer05$1
- //也可以直接调用, 匿名内部类本身也是返回对象
- // class 匿名内部类 extends Person {}
- }
- new Person(){
- @Override
- public void hi() {
- System.out.println("匿名内部类重写了 hi 方法,哈哈...");
- }
- @Override
- public void ok(String str) {
- super.ok(str);
- }
- }.ok("jack");
- }
- class Person {//类
- public void hi() {
- System.out.println("Person hi()");
- }
- public void ok(String str) {
- System.out.println("Personok()"+str);
- }
- }
- //抽象类/接口...
要求创建季节(Season)对象,请设计并完成
classSeason{ //类
private String name;
private String desc; //描述
//构造器
//getXX
//setXX
}
- publicstaticvoidmain(String[]args){
- // //使用
- // Seasonspring=newSeason("春天","温暖");
- // Seasonwinter=newSeason("冬天","寒冷");
- // Seasonsummer=newSeason("夏天","炎热");
- // Seasonautumn=newSeason("秋天","凉爽");
- autumn.setName("XXX");
- autumn.setDesc("非常的热..");
- //因为对于季节而已,他的对象(具体值),是固定的四个,不会有更多
- //安老师的这个设计类的思路,不能体现季节是固定的四个对象
- //因此,这样的设计不好===> 枚举类[枚: 一个一个 举: 例举 , 即把具体的对象一个一个例举出来的类
- // 就称为枚举类]
- Season other = new Season("红天", "~~~");
1) 枚举对应英文(enumeration, 简写 enum)
2) 枚举是一组常量的集合。
3) 可以这里理解:枚举属于一种特殊的类,里面只包含一组有限的特定的对象。
1) 自定义类实现枚举
2) 使用enum 关键字实现枚举
1. 不需要提供setXxx方法,因为枚举对象值通常为只读;
2. 对枚举对象/属性使用 final + static 共同修饰,实现底层优化;
3. 枚举对象名通常使用全部大写 , 常量的命名规范;
4. 枚举对象根据需要,也可以有多个属性。
- package com.hspedu.enum_;
- public class Enumeration02 {
- public static void main(String[] args) {
- System.out.println(Season.AUTUMN);
- System.out.println(Season.SPRING);
- }}
- //演示字定义枚举实现
- class Season {//类
- private String name;
- private String desc;//描述
- //定义了四个对象, 固定.
- public static final Season SPRING = new Season("春天", "温暖");
- public static final Season WINTER = new Season("冬天", "寒冷");
- public static final Season AUTUMN = new Season("秋天", "凉爽");
- public static final Season SUMMER = new Season("夏天", "炎热");
- //1. 将构造器私有化,目的防止 直接 new
- //2. 去掉setXxx 方法, 防止属性被修改
- //3. 在 Season 内部,直接创建固定的对象
- //4. 优化,可以加入 final 修饰符
- private Season(String name, String desc) {
- this.name = name;
- this.desc = desc;
- }
- public String getName() {
- return name;
- }
- public String getDesc() {
- return desc;
- }
- @Override
- public String toString() {
- return "Season{" +
- "name='" + name + '\' +
- ", desc='" + desc + '\' +
- '}';
- }}
进行自定义类实现枚举,有如下特点:
1)构造器私有化
2)本类内部创建一组对象[四个春夏秋冬]
3)对外暴露对象(通过为对象添加publicfinalstatic修饰符)
4)可以提供get方法,但是不要提供set
- packagecom.hspedu.enum_;
- publicclassEnumeration03{
- publicstaticvoidmain(String[]args){
- System.out.println(Season2.AUTUMN);
- System.out.println(Season2.SUMMER);
- }}
- //演示使用enum关键字来实现枚举类
- enum Season2{//类
-
- //定义了四个对象, 固定.
- //public static final Season SPRING = new Season("春天", "温暖");
- //public static final Season WINTER = new Season("冬天", "寒冷");
- // public static final Season AUTUMN = new Season("秋天", "凉爽");
- //public static final Season SUMMER = new Season("夏天", "炎热");
- //如果使用了enum 来实现枚举类
- //1. 使用关键字 enum 替代 class
- //2. public static final Season SPRING = new Season("春天", "温暖") 直接使用
- //SPRING("春天", "温暖") 解读 常量名(实参列表)
- //3. 如果有多个常量(对象), 使用 ,号间隔即可
- //4. 如果使用enum 来实现枚举,要求将定义常量对象,写在前面
- //5. 如果我们使用的是无参构造器,创建常量对象,则可以省略 ()
- SPRING("春天", "温暖"),WINTER("冬天", "寒冷"),AUTUMN("秋天", "凉爽"),
- SUMMER("夏天", "炎热")/*,What()*/;
-
- private String name;
- private String desc;//描述
- private Season2() {//无参构造器
- }
- private Season2(String name, String desc) {
- this.name = name;
- this.desc = desc;
- }
- publicStringgetName(){
- returnname;
- }
- publicStringgetDesc(){
- returndesc;
- }
- @Override
- publicStringtoString(){
- return"Season{"+
- "name='"+name+'\''+
- ",desc='"+desc+'\''+
- '}';
- } }
1) 当我们使用enum关键字开发一个枚举类时,默认会继承Enum类,而且是一个final类;
2) 传统的public static final Season2 SPRING = new Season2 ("春天","温暖");
简化成SPRING("春天","温暖"),这里必 须知道,它调用的是哪个构造器;
3) 如果使用无参构造器创建枚举对象,则实参列表和小括号都可以省;
4) 当有多个枚举对象时,使用,间隔,最后有一个分号结尾;
5) 枚举对象必须放在枚举类的行首。
说明:使用关键字enum时,会隐式继承Enum类, 这样我们就可以使用Enum类相关的方法。
1) 使用enum关键字后,就不能再继承其它类了,因为enum会隐式继承Enum,而Java是单继承机制。
2) 枚举类和普通类一样,可以实现接口,如下形式。 enum类名implements接口1,接口2{ }
1) 注解(Annotation)也被称为元数据(Metadata),用于修饰解释 包、类、方法、属性、构造器、局部变量等数据信息。
2) 和注释一样,注解不影响程序逻辑,但注解可以被编译或运行,相当于嵌入在代码中的补充信息。
3) 在JavaSE中,注解的使用目的比较简单,例如标记过时的功能,忽略警告等。在JavaEE中注解占据了更重要的角 色,例如用来配置应用程序的任何切面,代替javaEE旧版中所遗留的繁冗代码和XML配置等。
使用 Annotation 时要在其前面增加 @ 符号, 并把该 Annotation 当成一个修饰符使用。用于修饰它支持的程序元素
三个基本的 Annotation:
1) @Override: 限定某个方法,是重写父类方法, 该注解只能用于方法
2) @Deprecated: 用于表示某个程序元素(类, 方法等)已过时
3) @SuppressWarnings: 抑制编译器警告
- packagecom.hspedu.annotation_;
- publicclassOverride_{
- publicstaticvoidmain(String[]args){
- }}
- classFather{//父类
- publicvoidfly(){
- System.out.println("Fatherfly...");
- }
- publicvoidsay(){}
- }
- class Son extends Father {//子类
- //1. @Override 注解放在 fly 方法上,表示子类的fly方法时重写了父类的fly
- //2. 这里如果没有写 @Override 还是重写了父类fly
- //3. 如果你写了@Override 注解,编译器就会去检查该方法是否真的重写了父类的
- 方法,如果的确重写了,则编译通过,如果没有构成重写,则编译错误
- //4. 看看 @Override 的定义
- /*
- */
- 解读: 如果发现 @interface 表示一个 注解类
- @Target(ElementType.METHOD)
- @Retention(RetentionPolicy.SOURCE)
- public @interface Override {
- }
- @Override
- //说明
- public void fly() {
- System.out.println("Son fly....");
- }
- @Override
- public void say() {}
- }
Override使用说明
1.@Override表示指定重写父类的方法(从编译层面验证),如果父类没有fly方法,则会报错
2.如果不写@Override注解,而父类仍有public void fly00,仍然构成重写
3.@Override只能修饰方法,不能修饰其它类,包,属性等等
4.查看@Override注解源码为@Target(ElementType.METHOD),说明只能修饰
方法
5.@Target是修饰注解的注解,称为元注解,记住这个概念.
- //1.@Deprecated修饰某个元素,表示该元素已经过时
- //2.即不在推荐使用,但是仍然可以使用
- //3.查看@Deprecated注解类的源码
- //4. 可以修饰方法,类,字段, 包, 参数 等等
- //5. @Deprecated 可以做版本升级过渡使
- /*
- @Documented
- @Retention(RetentionPolicy.RUNTIME)
- @Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER,TYPE})
- public @interface Deprecated {
- }
- */
- @Deprecated
- class A{
- @Deprecated
- public int n1 = 10;
- @Deprecated
- public void hi(){
- }
@SuppressWarnings: 抑制编译器警告
- /1. 当我们不希望看到这些警告的时候,可以使用 SuppressWarnings注解来抑制警告信息
- //2. 在{""} 中,可以写入你希望抑制(不显示)警告信息
- //3. 可以指定的警告类型有
- all,抑制所有警告
- boxing,抑制与封装/拆装作业相关的警告
- //cast,抑制与强制转型作业相关的警告
- //dep-ann,抑制与淘汰注释相关的警告
- //deprecation,抑制与淘汰的相关警告
- //fallthrough,抑制与 switch 陈述式中遗漏 break 相关的警告
- //finally,抑制与未传回finally 区块相关的警告
- //hiding,抑制与隐藏变数的区域变数相关的警告
- //incomplete-switch,抑制与 switch 陈述式(enum case)中遗漏项目相关的警告
- //javadoc,抑制与 javadoc 相关的警告
- //nls,抑制与非nls 字串文字相关的警告
- //null,抑制与空值分析相关的警告
- //rawtypes,抑制与使用raw类型相关的警告
- //resource,抑制与使用 Closeable 类型的资源相关的警告
- //restriction,抑制与使用不建议或禁止参照相关的警告
- //serial,抑制与可序列化的类别遗漏serialVersionUID 栏位相关的警告
- //static-access,抑制与静态存取不正确相关的警告
- //static-method,抑制与可能宣告为 static 的方法相关的警告
- //super,抑制与置换方法相关但不含super呼叫的警告
- //synthetic-access,抑制与内部类别的存取未最佳化相关的警告
- //sync-override,抑制因为置换同步方法而遗漏同步化的警告
- //unchecked,抑制与未检查的作业相关的警告
- //unqualified-field-access,抑制与栏位存取不合格相关的警告
- //unused,抑制与未用的程式码及停用的程式码相关的警告
- //4. 关于SuppressWarnings 作用范围是和你放置的位置相关
- //5.
- /*
- 比如 @SuppressWarnings 放置在 main方法,那么抑制警告的范围就是 main
- 通常我们可以放置具体的语句, 方法, 类.
- 看看 @SuppressWarnings 源码
- //(1) 放置的位置就是 TYPE,FIELD,METHOD,PARAMETER,CONSTRUCTOR,LOCAL_VARIABLE
- //(2) 该注解类有数组 String[]values() 设置一个数组比如 {"rawtypes", "unchecked", "unused"}
- @Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
- @Retention(RetentionPolicy.SOURCE)
- public @interface SuppressWarnings {
- String[] value();
- */
@SuppressWarnings注解的案例
说明各种值
1) unchecked是忽略没有检查的警告
2) rawtypes是忽略没有指定泛型的警告(传参时没有指定泛型的警告错误)
3) unused是忽略没有使用某个变量的警告错误
4) @SuppressWarnings可以修饰的程序元素为,查看@Target
5) 生成@SupperssWarnings时,不用背,直接点击左侧的黄色提示,就可以选择(注意可以指定生成的位置)
JDK 的元 Annotation 用于修饰其他 Annotation
元注解: 本身作用不大,讲这个原因希望大家看源码时,可以知道他是干什么的
1) Retention //指定注解的作用范围,三种 SOURCE,CLASS,RUNTIME
2) Target // 指定注解可以在哪些地方使用
3) Documented //指定该注解是否会在javadoc体现
4) Inherited //子类会继承父类注解
说明:
只能用于修饰一个 Annotation 定义, 用于指定该 Annotation 可以保留多长时间,@Rentention 包含一个 RetentionPolicy 类型的成员变量, 使用 @Rentention 时必须为该 value 成员变量指定值:
@Retention 的三种值
1) RetentionPolicy.SOURCE: 编译器使用后,直接丢弃这种策略的注释
2) RetentionPolicy.CLASS: 编译器将把注解记录在 class 文件中. 当运行 Java 程序时,JVM 不会保留注解。这是默认 值。
3) RetentionPolicy.RUNTIME:编译器将把注解记录在 class 文件中. 当运行 Java 程序时,JVM 会保留注解. 程序可以 通过反射获取该注解。
基本概念
Java语言中,将程序执行中发生的不正常情况称为“异常”。(开发过程中的语法错误和逻辑错误不是异常)
执行过程中所发生的异常事件可分为两大类
1)Error(错误):Java虚拟机无法解决的严重问题。如:JVM系统内部错误、资源耗尽等严重情况。比如:StackOverflowError[栈溢出]和OOM(out of memory). Error是严重错误,程序会崩溃。
2) Exception:其它因编程错误或偶然的外在因素导致的一般性问题,可以使用针对性的代码进行处理。例如空指针访问,试图读取不存在的文件,网络连接中断等等,Exception分为两大类:运行时异常[程序运行时,发生的异常]和编译时异常[编程时,编译器检查出的异常]。
1. 异常分为两大类,运行时异常和编译时异常.
2. 运行时异常,编译器检查不出来。一般是指编程时的逻辑错误,是程序员应该避免其出现的异常。java.lang.RuntimeException类及它的子类都是运行时异常
3. 对于运行时异常,可以不作处理,因为这类异常很普遍,若全处理可能会对程序的可读性和运行效率产生影响
4. 编译时异常,是编译器要求必须处置的异常。
1) NullPointerException 空指针异常:当应用程序试图在需要对象的地方使用 null 时,抛出该异常
2) ArithmeticException 数学运算异常: 当出现异常的运算条件时,抛出此异常。例如,一个整数“除以零”时,抛出此类的一个实例
3) ArrayIndexOutOfBoundsException 数组下标越界异常:用非法索引访问数组时抛出的异常。如果索引为负或大于等于数组大小,则该索引为非法索引
4) ClassCastException 类型转换异常:当试图将对象强制转换为不是实例的子类时,抛出该异常。
5) NumberFormatException 数字格式不正确异常[ ] : 当应用程序试图将字符串转换成一种数值类型,但该字符串不能转换为适当格式时,抛出该异常=>使用异常我们 可以确保输入是满足条件数字.
编译异常是指在编译期间,就必须处理的异常,否则代码不能通过编译。
SQLException //操作数据库时,查询表可能发生异常
IOException //操作文件时,发生的异常
FileNotFoundException //当操作一个不存在的文件时,发生异常
ClassNotFoundException //加载类,而该类不存在时,异常
EOFException //操作文件,到文件未尾,发生异常
IllegalArguementException //参数异常
异常处理就是当异常发生时,对异常处理的方式。
1) try-catch-finally
程序员在代码中捕获发生的异常,自行处理
2) throws
将发生的异常抛出,交给调用者(方法)来处理,最顶级的处理者就是JVM
1) Java提供try和catch块来处理异常。try块用于包含可能出错的代码。catch块用于处理try块中发生的异常。可以根据需要在程序中有多个try...catch块。
2)基本语法
try {
//可疑代码
//将异常生成对应的异常对象,传递给catch块
} catch(异常) {
//对异常的处理
}
//如果没有finally,语法是可以通过
1. 如果异常发生了,则异常发生后面的代码不会执行,直接进入到catch块
2. 如果异常没有发生,则顺序执行try的代码块,不会进入到catch
3. 如果希望不管是否发生异常,都执行某段代码(比如关闭连接,释放资源等)则使用如下代码-finally
4. 可以有多个catch语句,捕获不同的异常(进行不同的业务处理),要求父类异常在后,子类异常在前,比如(Exception在后,NullPointerException在前),如果发生异常,只会匹配一个catch。
5. 可以进行try-finally配合使用,这种用法相当于没有捕获异常,因此程序会直接崩掉/退出。应用场景,就是执行一段代码,不管是否发生异常,都必须执行某个业务逻辑
1)如果没有出现异常,则执行try块中所有语句,不执行catch块中语句,如果有finally,最后还需要执行finally里面的语句
2)如果出现异常,则try块中异常发生后,try块剩下的语句不再执行。将执行catch块中的语句,如果有finally,最后还需要执行finally里面的语句!
1)如果一个方法(中的语句执行时)可能生成某种异常,但是并不能确定如何处理这种异常,则此方法应显示地声明抛出异常,表明该方法将不对这些异常进行处理,而由该方法的调用者负责处理。
2)在方法声明中用throws语句可以声明抛出异常的列表,throws后面的异常类型可以是方法中产生的异常类型,也可以是它的父类。
1) 对于编译异常,程序中必须处理,比如try-catch或者throws
2) 对于运行时异常,程序中如果没有处理,默认就是throws的方式处理
3) 子类重写父类的方法时,对抛出异常的规定:子类重写的方法,所抛出的异常类型要么和父类抛出的异常一致,要么为父类抛出的异常的类型的子类型
4) 在throws 过程中,如果有方法 try-catch,就相当于处理异常,就可以不必throws
代码:
- packagecom.hspedu.throws_;
- importjava.io.FileInputStream;
- importjava.io.FileNotFoundException;
- publicclassThrowsDetail{
- publicstaticvoidmain(String[]args){
- f2();
- }
- publicstaticvoidf2()/*throwsArithmeticException*/{
- //1.对于编译异常,程序中必须处理,比如try-catch或者throws
- //2.对于运行时异常,程序中如果没有处理,默认就是throws的方式处理
- intn1=10;
- intn2=0;
- doubleres=n1/n2;}
- public static void f1() throws FileNotFoundException {
- //这里大家思考问题 调用f3() 报错
- //1. 因为f3() 方法抛出的是一个编译异常
- //2. 即这时,就要f1() 必须处理这个编译异常
- //3. 在 f1() 中,要么 try-catch-finally ,或者继续 throws 这个编译异常
- f3(); // 抛出异常
- }
- public static void f3() throws FileNotFoundException {
- FileInputStream fis = new FileInputStream("d://aa.txt");
- }
- public static void f4() {
- //1. 在 f4()中调用方法f5() 是OK
- //2. 原因是f5() 抛出的是运行异常
- f5();
- }
- //3. 而 java 中,并不要求程序员显示处理,因为有默认处理机制
- public static void f5() throws ArithmeticException {
- }}
- class Father { //父类
- public void method() throws RuntimeException {
- }
- }
- class Son extends Father {//子类
- //3. 子类重写父类的方法时,对抛出异常的规定:子类重写的方法,
- //所抛出的异常类型要么和父类抛出的异常一致,要么为父类抛出的异常类型的子类型
- //4. 在 throws 过程中,如果有方法 try-catch, 就相当于处理异常,就可以不必throws
- @Override
- public void method() throws ArithmeticException {
- }}
当程序中出现了某些“错误”,但该错误信息并没有在Throwable子类中描述处理,这个时候可以自己设计异常类,用于描述该错误信息。
1) 定义类:自定义异常类名(程序员自己写)继承Exception或RuntimeException
2) 如果继承Exception,属于编译异常
3) 如果继承RuntimeException,属于运行异常 (一般来说,继承RuntimeException)
1) 针对八种基本数据类型相应的引用类型—包装类
2) 有了类的特点,就可以调用类中的方法。
1) jdk5前的手动装箱和拆箱方式, 装箱:基本类型->包装类型,反之,拆箱
2) jdk5 以后(含jdk5)的自动装箱和拆箱方式
3) 自动装箱底层调用的是valueOf方法,比如Integer.valueOf()
4) 其它包装类的用法类似,不一一举例
1)方式一:直接赋值 String s ="xueixi";
2)方式二:调用构造器 String s = new String("xuexi");
1.方式一:先从常量池查看是否有"xuexi"数据空间,如果有,直接指向;如果没有则重新创建,然后指向。S最终指向的是常量池的空间地址
2.方式二:先在堆中创建空间,里面维护了value属性,指向常量池的xuexi空间。如果常量池没有"xuexi",重新创建,如果有,直接通过value指向。最终指向的是堆中的空间地址。
3.画出两种方式的内存分布图
1) String是一个final类,代表不可变的字符序列
2)字符串是不可变的。一个字符串对象一旦被分配,其内容是不可变的
1.以下语句创建了几个对象?画出内存布局图。
String s1 = "hello";
s1="haha"; //1min
//创建了2个对象.
String类是保存字符串常量的。每次更新都需要重新开辟空间,效率较低,因此java设计者还提供了StringBuilder和 StringBuffer来增强String的功能,并提高效率。
//看看这段代码
String s = new String("");
for( int i = o; i< 80000; i++){
s += "hello"; }
1. equals //区分大小写,判断内容是否相等
2. equalslgnoreCase //忽略大小写的判断内容是否相等
3. length //获取字符的个数,字符串的长度
4. indexOf //获取字符在字符串中第1次出现的索引,索引从0开始,如果找不到,返回-1
5. lastIndexOf //获取字符在字符串中最后1次出现的索引,索引从0开始,如找不到,返回-1
6. substring //截取指定范围的子串
7. trim //去前后空格
8. charAt:获取某索引处的字符,注意不能使用Str[index]这种方式.
java.lang.StringBuffer代表可变的字符序列,可以对字符串内容进行增删。很多方法与String相同,但StringBuffer是可变长度的。
StringBuffer是一个容器。
1. StringBuffer的直接父类是AbstractStringBuilder
2. StringBuffer实现了Serializable,即StringBuffer的对象可以串行化
3. 在父类中AbstractStringBuilder有属性char[]value,不是final 该value数组存放字符串内容,引出存放在堆中的
4. StringBuffer是一个final类,不能被继承
5. 因为StringBuffer字符内容是存在char[]value,所有在变化(增加/删除) 不用每次都更换地址(即不是每次创建新对象),所以效率高于Strin
1) String保存的是字符串常量,里面的值不能更改,每次String类的更新实际上就是更改地址,效率较低
2) StringBuffer保存的是字符串变量,里面的值可以更改,每次StringBuffer的更新实际上可以更新内容,不用每次更新地址,效率较高
- //看String——>StringBuffer
- Stringstr="hellotom";
- //方式1使用构造器
- //注意:返回的才是StringBuffer对象,对str本身没有影响
- StringBufferstringBuffer=newStringBuffer(str);
- //方式2使用的是append方法
- StringBufferstringBuffer1=newStringBuffer();
- stringBuffer1=stringBuffer1.append(str);
-
- //看看StringBuffer->String
- StringBufferstringBuffer3=newStringBuffer("韩顺平教育");
- //方式1使用StringBuffer提供的toString方法
- Strings=stringBuffer3.toString();
- //方式2:使用构造器来搞定
- Strings1=newString(stringBuffer3);
1) 一个可变的字符序列。此类提供一个与StringBuffer 兼容的 API,但不保证同步(StringBuilder 不是线程安全)。该类被设计用作 StringBuffer的一个简易替换,用在字符串缓冲区被单个线程使用的时候。如果可能,建议优先采用该类,因为在大多数实现中,它比 StringBuffer要快。
2) 在 StringBuilder 上的主要操作是append和 insert方法,可重载这些方法,以接受任意类型的数据。
StringBuilder 和 StringBuffer均代表可变的字符序列,方法是一样的,所以使用和StringBuffer一样.
1.StringBuilder是final
2.继承了AbstractStringBuilder,属性char[ ] value,内容存到value
3.实现Serializable接口,序列化(所谓序列化即可以保存类型和数据
1. StringBuilder 继承 AbstractStringBuilder 类
2. 实现了 Serializable ,说明 StringBuilder 对象是可以串行化(对象可以网络传输,可以保存到文件)
3. StringBuilder 是 final 类, 不能被继承
4. StringBuilder 对象字符序列仍然是存放在其父类 AbstractStringBuilder 的 char[] value; 因此,字符序列是堆中
5. StringBuilder 的方法,没有做互斥的处理,即没有synchronized 关键字,因此在单线程的情况下使用 StringBuilder
1) StringBuilder 和 StringBuffer非常类似,均代表可变的字符序列,而且方法也一样
2) String:不可变字符序列,效率低,但是复用率高。
3) StringBuffer:可变字符序列、效率较高(增删)、线程安全,看源码
4) StringBuilder:可变字符序列、效率最高、线程不安全
5) String使用注意说明:
string s="a"; //创建了一个字符串
s +="b"; //实际上原来的"a"字符串对象已经丢弃了,现在又产生了一个字符串s+"b”(也就 是"ab")。如果多次执行这些改变串内容的操作,会导致大量副本字符串对象存留在内存中,降
低效率。如果这样的操作放到循环中,会极大影响程序的性能
结论:如果我们对String做大量修改,不要使用String
使用的原则,结论:
1. 如果字符串存在大量的修改操作,一般使用StringBuffer 或StringBuilder
2. 如果字符串存在大量的修改操作,并在单线程的情况,使用 StringBuilder
3. 如果字符串存在大量的修改操作,并在多线程的情况,使用 StringBuffer
4. 如果我们字符串很少修改,被多个对象引用,使用String,比如配置信息等
StringBuilder的方法使用和StringBuffer 一样,不再说.
Math类包含用于执行基本数学运算的方法,如初等指数、对数、平方根和三角函数。
Arrays里面包含了一系列静态方法,用于管理或操作数组(比如排序和搜索)
1) toString返回数组的字符串形式
Arrays.toString(arr)
2) sort 排序(自然排序和定制排序) Integer arr[] = {1,-1,7,0,89 };
3) binarySearch 通过二分搜索法进行查找,要求必须排好序int index =Arrays.binarySearch(arr,3);
4) copyOf数组元素的复制
Integerl] newArr = Arrays.copyOf(arr, arr.length);
5) fil数组元素的填充
Integer[] num = new Integer[]{9.3.21}; Arrays.fill(num,99);
6) equals比较两个数组元素内容是否完全一致 boolean equals = Arrays.equals(arr, arr2);
7) asList将一组值,转换成list
List
System.out.println("asList=" + asList);
具体情况请分析源码
1) exit 退出当前程序
2) arraycopy :复制数组元素,比较适合底层调用,一般使用Arrays.copyOf完成复制数组.
int[] src = {1,2,33 };
int[ dest = new int[3];
System.arraycopy(src, 0, dest, 0. 3);
3) currentTimeMillens:返回当前时间距离1970-1-1的毫秒数
4) gc:运行垃圾回收机制 System.gc ( );
应用场景:
1) Biglnteger适合保存比较大的整型
2) BigDecimal适合保存精度更高的浮点型(小数)
常见方法:
1) add 加 2) subtract 减 3) multiply 乘 4) divide 除
还有 日期类 可了解
前面我们保存多个数据使用的是数组,那么数组有不足的地方,我们分析一下
1) 长度开始时必须指定,而且一旦指定,不能更改
2) 保存的必须为同一类型的元素
3) 使用数组进行增加/删除元素的示意代码-比较麻烦
写出Person数组扩容示意代码。
Person[ ] pers = new Person[1]; //大小是1
per[0]=new Person();
//增加新的Person对象?
Person[ll pers2 = new Person[pers.length+1];//新创建数组
for( ) { } //拷贝pers数组的元素到pers2
pers2[pers2.length-1]=new Person(); //添加新的对象
1) 可以动态保存任意多个对象,使用比较方便!
2) 提供了一系列方便的操作对象的方法:add,remove、set、get等
3) 使用集合添加,删除新元素的示意代码-简洁了
Java 的集合类很多,主要分为两大类
1. 集合主要是两组(单列集合 , 双列集合)
2. Collection 接口有两个重要的子接口 ListSet, 他们的实现子类都是单列集合
3. Map 接口的实现子类 是双列集合,存放的 K-V
public interface Collection
1) collection实现子类可以存放多个元素,每个元素可以是Object
2) 有些Collection的实现类,可以存放重复的元素,有些不可以
3) 有些Collection的实现类,有些是有序的(List),有些不是有序(Set)
4) Collection接口没有直接的实现子类,是通过它的子接口Set 和 List来实现的
Collection接口遍历元素方式
1-使用Iterator(迭代器)
2-for循环增强
List 接口是collection接口的子接口
1) List集合类中元素有序(即添加顺序和取出顺序一致)、且可重复
2) List集合中的每个元素都有其对应的顺序索引,即支持索引。
3) List容器中的元素都对应一个整数型的序号记载其在容器中的位置,可以根据序号存取容器中的元素。
1)方式一:使用iterator
lterator iter = col.iterator();
while(iter.hasNext0){
Object o = iter.next(); }
2)方式二: 使用增强 for
for(Object o:col){ }
3)方式三: 使用普通for
for(int i=0;i
System.out.println(object); }
说明:使用LinkedList完成使用方式和ArrayList一样
1)permits all elements, including null , ArrayList可以加入null,并且多个
2) ArrayList是由数组来实现数据存储的
3)ArrayList基本等同于Vector,除了ArrayList是线程不安全(执行效率高)看源码.在多线程情况下,不建议使用ArrayList
1) ArrayList中维护了一个0bject类型的数组elementData. [debug看源码]
transient Object[] elementData; //transient表示瞬间,短暂的,表示该属性不会被序列号
2) 当创建ArrayList对象时,如果使用的是无参构造器,则初始elementData容量为0,第1次添加, 则扩容elementData为10,如需要再次扩容,则扩容elementData为1.5倍。
3) 如果使用的是指定大小的构造器,则初始elementData容量为指定大小,如果需要扩容,则直接扩容elementData为1.5倍。
建议:自己去debug一把我们的ArrayList的创建和扩容的流程.
1) Vector类的定义说明
public class vector
extends AbstractList
implements List
2) Vector底层也是一个对象数组,protected object[] elementData;
3) Vector是线程同步的,即线程安全, Vector类的操作方法带有synchronizec
public synchronized E get(int index){
if (index >= elementCount)
throw new ArraylndexOutOfBoundsException(index);
return elementData(index); }
4) 在开发中,需要线程同步安全时,考虑使用Vector
1) LinkedList底层实现了双向链表和双端队列特点
2) 可以添加任意元素(元素可以重复),包括null
3) 线程不安全,没有实现同步
1) LinkedList底层维护了一个双向链表.
2) LinkedList中维护了两个属性first和last分别指向首节点和尾节点
3) 每个节点(Node对象),里面又维护了prev、next、item三个属性,其中通过prev指向前一个,通过next指向后一个节点。最终实现双向链表.
4) 所以LinkedList的元素的添加和删除,不是通过数组完成的,相对来说效率较高。
5) 模拟一个简单的双向链表
如何选择ArrayList和LinkedList:
1. 如果我们改查的操作多,选择ArrayList
2. 如果我们增删的操作多,选择LinkedList
3)一般来说,在程序中,80%-90%都是查询,因此大部分情况下会选择ArrayList
4)在一个项目中,根据业务灵活选择,也可能这样,一个模块使用的是ArrayList,另外一个模块是LinkedList,也就是说,要根据业务来进行选择
1)无序(添加和取出的顺序不一致),没有索引
2)不允许重复元素,所以最多包含一个null
和List接口一样,Set接口也是Collection的子接口,因此,常用方法和Collection接口一样.
同Collection的遍历方式一样,因为Set接口是Collection接口的子接口。
1. 可以使用迭代器
2. 增强for
3. 不能使用索引的方式来获取.
1) HashSet实现了Set接口
2) HashSet实际上是HashMap,看下源码.
public Hashset( ) {
map = new HashMap<>( ); }
3) 可以存放null值,但是只能有一个null
4) HashSet不保证元素是有序的,取决于hash后,再确定索引的结果.(即,不保证存放元素的顺序和取出顺序一致)
5) 不能有重复元素/对象.
注意:这里讲的是JDK8的Map接口特点
1) Map与Collection并列存在。用于保存具有映射关系的数据:Key-Value
2) Map 中的key和value可以是任何引用类型的数据,会封装到HashMap$Node对象中
3) Map中的key不允许重复,原因和HashSet一样,前面分析过源码.
4) Map中的value可以重复
5) Map 的key 可以为null, value 也可以为null,注意key为null,只能有一个,value为null ,可以多个.
6) 常用String类作为Map的key
7) key和 value之间存在单向一对一关系,即通过指定的 key总能找到对应的value
8) Map存放数据的key-value示意图,一对k-v是放在一个HashMap$Node中的,有因为Node 实现了Entry 接口,有些书上也说一对k-v就是一个Entry(如图)
Map遍历的示意图(比List,和Set复杂点,但是基本原理一样)
Map遍历方式案例演示
1. containsKey:查找键是否存在
2. keySet:获取所有的键
3. entrySet:获取所有关系k-v
4. values:获取所有的值
1) Map接口的常用实现类:HashMap、Hashtable和Properties。
2) HashMap是 Map接口使用频率最高的实现类。
3) HashMap 是以 key-val对的方式来存储数据(HashMap$Node类型)
4) key不能重复,但是值可以重复,允许使用null键和null值。
5) 如果添加相同的key,则会覆盖原来的key-val ,等同于修改.(key不会替换,val会替换)
6) 与HashSet一样,不保证映射的顺序,因为底层是以hash表的方式来存储的.(jdk8的hashMap底层数组+链表+红黑树)
7) HashMap没有实现同步,因此是线程不安全的,方法没有做同步互斥的操作,没有synchronized
1. 存放的元素是键值对:即K-V
2. hashtable的键和值都不能为null,否则会抛出NullPointerException
3. hashTable使用方法基本上和HashMap一样
4. hashTable是线程安全的(synchronized), hashMap是线程不安全的
5. 简单看下底层结构
1. Properties类继承自Hashtable类并且实现了Map接口,也是使用一种键值对的形式来保存数据。
2. 他的使用特点和Hashtable类似
3. Properties还可以用于从 xxox.properties文件中,加载数据到Properties类对象,并进行读取和修改
在开发中,选择什么集合实现类,主要取决于业务操作特点,然后根据集合实现类特性进行选择,分析如下:
1. 先判断存储的类型(一组对象[单列或一组键值对[双列])
2. 一组对象[单列]:Collection接口
允许重复:List
增删多: LinkedList[底层维护了一个双向链表]
改查多:ArrayList[底层维护Object类型的可变数组]
不允许重复:Set
无序:HashSet [底层是HashMap,维护了一个哈希表即(数组+链表+红黑树)]
排序:TreeSet[老韩举例说明]
插入和职出顺序一致:LinkedHashSet,维护数组+双向链表
3. 一组键值对[双列]:Map
键无序:HashMap [底层是:哈希表 jdkT:数组+链表,jdk8:数组+链表+红黑树]
键排序:TreeMap
键插入和取出顺序一致:LinkedHashMap
读取文件Properties
1) Collections是一个操作 Set、List 和 Map等集合的工具类
2) Collections中提供了一系列静态的方法对集合元素进行排序、查询和修改等操作
1) reverse(List):反转 List中元素的顺序
2) shuffle(List):对List集合元素进行随机排序
3) sort(List):根据元素的自然顺序对指定List 集合元素按升序排序
4) sort(List,Comparator):根据指定的Comparator 产生的顺序对List集合元素进行排序
5) swap(List,int,int):将指定 list集合中的i处元素和j处元素进行交换
1)Object max(Collection):根据元素的自然顺序,返回给定集合中的最大元素
2) Object max(Collection, Comparator):根据Comparator指定的顺序,返回给定集合中的最大元素
3) Object min(Collection)
4) Object min(Collection,Comparator)
5) int frequency(Collection,Object):返回指定集合中指定元素的出现次数
6) void copy(List dest,List src):将src中的内容复制到dest中
7) boolean replaceAll(List list,Object oldVal,Object newVal):使用新值替换List 对象的所有旧值
1) 编译时,检查添加元素的类型,提高了安全性
2) 减少了类型转换的次数,提高效率
不使用泛型
Dog-加入->Object -取出-> Dog
//放入到ArrayList 会先转成Object,在取出时,还需要转换成Dog
使用泛型
Dog -> Dog -> Dog
//放入时,和取出时,不需要类型转换,提高效率
int a = 10;
理解:泛(广泛)型(类型)=> Integer, String,Dog
1)泛型又称参数化类型,是Jdk5.0出现的新特性,解决数据类型的安全性问题
2)在类声明或实例化时只要指定好需要的具体的类型即可。
3) Java泛型可以保证如果程序在编译时没有发出警告,运行时就不会产生ClassCastException异常。同时,代码更加简洁、健壮
4)泛型的作用是:可以在类声明时通过一个标识表示类中某个属性的类型,或者是某个方
法的返回值的类型,或者是参数类型。
interface股
//比如: List , ArrayList
说明:
1) 其中,T,K,V不代表值,而是表示类型。
2) 任意字母都可以。常用T表示,是Type的缩写
要在类名后面指定类型参数的值(类型)。如:
1) List
2) lterator
1. interface List
说明:T,E只能是引用类型
看看下面语句是否正确?:
List
List
2. 在给泛型指定具体类型后,可以传入该类型或者其子类类型
3.泛型使用形式
List
List
4. 如果我们这样写List list3 = new ArrayList(); 默认给它的泛型是[
基本语法
class类名
成员 }
注意细节
1) 普通成员可以使用泛型(属性、方法)
2) 使用泛型的数组,不能初始化
3) 静态方法中不能使用类的泛型
4) 泛型类的类型,是在创建对象时确定的(因为创建对象时,需要指定确定类型)
5) 如果在创建对象时,没有指定类型,默认为Object
应用案例:
- class Tiger
{ - String name;
- R r;
- M m;
- T t;
- }
1) 泛型不具备继承性
List