Java SE学习笔记

疯狂Java讲义笔记

Posted by Sheldon on April 23, 2018

数据类型

基本类型:包含boolean类型和数值类型

  • 整型 byte(1字节),short(2字节),int(4字节),long(8字节)
  • 字符型 char(2字节)
  • 浮点型 float(4字节),double(8字节)
  • 布尔型 boolean,只能是true和false,其他基本类型数据也不能转换为boolean类型

基本类型自动转换

表达式类型自动提升:所有的byte类型、short类型和char类型将被提升到int类型; 整个算数表达式的数据类型自动提升到与表达式中最高等级操作数相同的类型

引用类型:包括类、接口和数组类型,还有一种特殊的null类型, 所有引用类型的默认值都是null

直接量:是指在程序中通过源代码直接给出的值,包含int,long,float,double,boolean,char,string, null一共8种类型的直接量。其中需要注意的是,string不能赋值给其他类型的变量, null可以赋给任何引用类型的变量,boolean只能赋给boolean的变量

数组:一个数组里只能存储一种类型的数据;定义数组时不能置顶数组的长度; 静态初始化是直接指定数组里的每个值;动态初始化是指定长度同时指定默认值 (整型为0,浮点0.0,字符\u000,布尔false,引用类型为null)

面向对象

三大基本特征:封装、继承和多态

类:包含构造器、成员变量、方法、初始化块、内部类;静态成员(static修饰)不能访问非静态成员;可以被public、final、abstract修饰

  • 成员变量:可以被public、protected、private、static、final修饰;可以是Java允许的任意类型(基本类型和引用类型)
  • 方法:可以被public、protected、private、static、final、abstract修饰; 如果返回值类型不是void,方法内必须包含return,且return的类型必须与声明的返回值类型一致
  • 构造器:可以被public、protected、private修饰;构造器名必须和类名相同
  • 一个源文件中只能有一个public类
  • 一个源文件可以有多个非public类
  • 源文件的名称应该和public类的类名保持一致
  • java 文件编译后, 每个java类都有一个class文件

对象:是使用类产生的类的实例,值得注意的是this这个关键字处理对象的时候和Ruby中的self一样,可是Ruby中的类也是对象, 故可以在类上使用self,而Java中的this是指向当前对象,所以静态方法不能使用this引用

方法参数传递:Java的参数传递都是值传递,就是将实际参数的副本传入方法,而参数本身不受影响; 特别的,如果入参是一个对象的引用副本,那么在方法中修改了这个引用对象的值, 外面的对象实际上是随之改变了的(引用中存储的是对象在堆内存中的地址)

封装:通过访问控制符,实现该隐藏的隐藏起来,该暴露的暴露出来

  private default protected public
同一类中
同一包中  
子类中    
全局范围      

包:自动引入的包 java.lang 包括System, String, Math等;包名必须全部小写

  • 如果一个类定义在某个包中,那么package语句应该在源文件的首行
  • 如果源文件包含import语句,那么应该放在package语句和类定义之间
  • import static 用于静态导入,导入所有的静态成员
  • import可以省略包名,import static 可以连类名都省略

构造器:最大的作用就是在创建对象时执行初始化;无论如何Java类至少包含一个构造器, 如果没有提供,系统会为这个类提供一个无参的构造器;构造器之间的调用使用this();

继承:每个子类只有一个直接父类,通过extends关键字实现;子类调用父类的方法, 使用super可以调用父类中被子类覆盖的方法;子类构造器可以调用父类构造器,把this替换成super即可; 子类构造器如果没有主动调用父类的构造器,系统将在子类构造器执行之前,隐式调用父类的无参数构造器

多态:父类引用指向子类对象;方法具有多态性,对象的实例变量则不具备多态性(会使用父类的实例变量); 引用间的强制类型转换只有在具有继承关系的两个类型之间进行;instanceof用于判断前面的对象是否是后面的类, 或者子类、实现类的实例,同时前面操作数的编译时类型要么与后面的类相同,要么具有父子继承关系

组合:在对象的私有实例变量中,组合另一个对象的实例,就可以通过这个实例变量, 访问另一个对象里的public方法;继承要表达的是一种 is-a 的关系,组合表现的是一种 has-a 的关系

初始化块:初始化块只能使用static修饰或者不用修饰符,使用static修饰的初始化块称为静态初始化块; 静态初始化块是类相关,所以总是先与普通初始化块;初始化块是构造器的补充,初始化块总是在构造器之前执行

Java类成员执行顺序:父类的静态初始化块 -> 子类的静态初始化块 -> 父类的初始化块 -> 父类的构造函数 -> 子类的初始化块 -> 子类的构造函数

  • 静态初始化块的优先级最高,与类相关,最先执行,并且仅在类第一次被加载时执行
  • 非静态初始化块和构造函数,与对象相关,在每次生成对象时执行一次
  • 非静态初始化块的代码会在类构造函数之前执行
  • 静态初始化块既可以用于初始化静态成员变量,也可以执行初始化代码
  • 非静态初始化块可以针对多个重载构造函数进行代码复用

Java中的相等:== 如果是数值类型,只有变量相等为true;对于两个引用类型,只有他们指向同一对象时才相等, 且其不能用于没有父子关系的两个对象;equals时Object类提供的一个实例方法,本质上是和”==”一致,因此系统希望重写此方法; 正确的重写equals应该具备以下条件:

  • 自反性:x.equals(x)一定为true
  • 对称性:x.equals(y)为true,y.equals(x)也应该为true
  • 传递性:x.equals(y)为true,y.equals(z)为true,那么x.equals(z)也为true
  • 一致性:如果用于等价比较的信息没有改变,那么equals的返回也不能改变
  • 对任何非null的x,x.equals(null)一定为false

单例类:始终只能创建一个实例的类叫做单例类,做法为使用一个类变量来缓存已经创建的实例,用private隐藏构造器, 另外提供一个静态方法返回实例,如果存在就返回,不存在就创建

final修饰符:可用于修饰类、变量和方法,表示不可改变

  • final 修饰的成员变量必须有程序员显示的指定初始值;其中类变量必须在静态初始化块中或者声明该变量时指定初始值, 实例变量必须在非静态初始化块、声明该实例变量的地方或构造器中指定初始值
  • final 修饰的方法无法被重写
  • final 修饰的类不可以有子类

抽象类:抽象方法和抽象类使用abstract 修饰

  • 抽象方法不能有方法体
  • 抽象类不能被实例化,既是抽象类中不包含抽象方法
  • 抽象类可以包含成员变量,方法(普通和抽象都行),构造器,代码块,内部类
  • 含义抽象方法的类一定是抽象类,如果子类继承抽象类,但未完全复写抽象方法,该子类也是一个抽象类
  • final 和abstract 不能同时使用;static和abstract 不能同时修饰方法;private 和 abstract 不能修饰方法

接口:使用interface定义,只能用public或省略修饰符

  • 接口可以使用extends继承多个接口
  • 接口的成员变量包括静态常量,抽象方法,类方法,默认方法,内部类(内部接口和枚举)
  • 接口的静态常量,无论是否使用 public static final 修饰,总是使用这三个修饰符来修饰
  • 默认方法使用 default 修饰,不能使用 static 修饰,默认同时必须使用 public 修饰,由于不能使用static, 故需要实现类的实例来调用默认方法
  • java8允许在接口中定义静态方法,必须使用static 修饰,无论是否指定,总是石笋public 修饰
  • 接口里的方法,接口里的普通方法总是使用public abstract 来修饰,且不能有方法体;但类方法和默认方法必须有方法实现
  • 接口里的内部类、内部接口、内部枚举都默认使用 public static 修饰
  • 接口不能创建实例,但是可以声明引用类型变量,这个引用类型的变量必须引用到其实现类的对象

接口和抽象类共同点:

  • 都不能实例化,都位于继承树的顶端,用于被其他类实现和继承
  • 都可以包含抽象方法,实现接口或者继承抽象类的普遍类都必须实现这些抽象方法

接口和抽象类区别:

  • 接口体现的是一种规范,对实现者而言,接口规定了实现者必须实现对外提供哪些服务; 对接口调用者而言,接口规定了调用者可以调用哪些方法,以及怎么调用
  • 抽象类体现的是一种模板的设计,作为多个子类的抽象父类,进一步提取了多个类之间的共性
  • 接口里只能包含抽象方法和默认方法,不能为普通方法提供实现;抽象类可以有实现体的普通方法
  • 接口里不能有构造器,抽象类可以;抽象类的构造器是让其子类调用来完成属于抽象类的初始化操作
  • 接口没有初始化块,抽象类有

内部类:定义在类内部的类叫做内部类(嵌套类),包含内部类的类被称为外部类(宿主类); 主要用于更好的封装,内部类不允许他一个包中的其他类访问; 内部类成员可以直接访问外部类的私有数据,但外部类不能访问内部类的成员变量;匿名内部类适用于创建哪些仅需要一次使用的类; 内部类可以使用private、protected、static修饰;非静态内部类不能拥有静态成员

  • 非静态内部类:定义在类里面的内部类作为一种与成员变量、方法、构造器和初始化块相似的类成员; 定义方法中的内部类称为局部内部类,局部内部类和匿名内部类不是类成员; 非静态内部类不允许有静态方法,静态成员变量,静态初始化块; 访问变量顺序:方法内局部变量 -> 内部类成员变量 -> 外部类中成员变量 -> 编译错误
  • 静态内部类:使用static 修饰的内部类叫做静态内部类;可以包含静态成员和非静态成员,只能访问外部类的静态成员; 外部类不能访问静态内部类的成员;接口中可以定义静态内部类,默认使用public static 修饰
  • 使用内部类:非静态内部类 OutClass.InClass in = new OutClass().new InClass(); 静态内部类 OutClass.InClass in = new OutClass().InClass();
  • 匿名内部类:适合创建那种只需要使用一次的类,匿名内部类必须继承一个父类,或者实现一个接口;创建匿名内部类时会立即创建一个该类的实例, 故匿名内部类不能是一个抽象类;因为匿名内部类没有类名,故不能定义构造器;可以使用初始化块完成构造器的需要完成的事情

Java8的Lambda表达式:和ruby的lambda差不多,是一个可执行的代码块;主要作用是代替匿名内部类烦琐的语法,由形参列表,箭头,代码块组成; Lambda表达式的目标类型必须是“函数式接口”,只包含一个抽象方法的接口;Lambda表达式只能为函数式接口创建对象

Lambda与匿名内部类的区别和联系

  • 都可以直接访问“effectively final”的局部变量,以及外部类的成员变量(包括实例变量和类变量)
  • 都可以直接调用从接口中继承的默认方法
  • 匿名内部类可以为任意接口创建实例(多个抽象方法),只要匿名内部类实现了所有抽象方法即可;Lambda只能为函数式接口创建实例
  • 匿名内部类还可以为普通类和抽象类创建实例
  • 匿名内部类实现的抽象方法可以调用接口中的默认方法;Lambda不允许调用

方法引用和构造器引用替换单行Lambda表达式:

垃圾回收:当程序创建对象、数组等引用实体时,系统会在堆内存中分配一个内存,当此内存没有任何引用变量引用时,此内存就成了垃圾

  • 垃圾回收机制只负责回收堆内存中的对象,不回收任何物理资源(eg:数据库连接,网络IO等)
  • 程序无法精确控制垃圾回收的运行,垃圾回收会在合适的时候经行
  • 在垃圾回收机制回收任何对象之前,会调用他的finalize() 方法,该方法可能让对象重新复活,导致回收机制取消
  • 调用System 类的gc() 静态方法:System.gc() 可以强制回收(相当于通知系统进行垃圾回收,系统什么时候回收并不能控制)
  • 调用Runtime 对象的实例方法:Runtime.getRuntime.gc() 可以强制回收(相当于通知系统进行垃圾回收,系统什么时候回收并不能控制)
  • 永远不要主动调用某个对象的 finalize() 方法,该方法应交给垃圾回收机制调用
  • finalize() 方法何时被调用,是否被调用具有不确定性。就是说垃圾回收也不一定会执行
  • JVM执行 finalize() 方法是出现异常,垃圾回收不会报告,程序继续运行

修饰符适用范围:

jar命令详解:

  • 创建jar文件:jar cf test.jar test
  • 创建jar文件并显示过程:jar cvf test.jar test
  • 不使用清单文件:jar cvfM test.jar test
  • 自定义清单文件内容:jar cvfm test.jar manifest.mf test
  • 查看JAR包内容:jar tf test.jar
  • 查看JAR包详细内容:jar tvf test.jar
  • 解压:jar xf test.jar
  • 更新JAR文件:jar uf test.jar Hello.class

集合

collection体系继承树: map体系继承树:

Set集合:无序集合,元素不能重复

HashSet类:Set接口的典型实现,最常用的实现类;按照Hash算法来存储集合的元素;具有很好的存取和查找功能

  • 不能保证元素的排列顺序
  • HashSet不是同步的,在多线程的情况下,必须使用代码保证其同步
  • 元素值可以是null
  • 使用hashCode()方法来得到对象的HashCode值,并根据此值决定对象在HashSet中的位置;故equals()为true的值,如果hashCode 不相等,可以添加成功
  • 如果hashCode相同,equals 返回false,则这个位置会使用链式结构来保存多个对象,导致性能下降
  • 故重写equals方法是,也应该重写hashCode方法,保证:如果两个对象的euqals返回true,这两个对象的hashCode也应该相同; 同一个对象多次调用hashCode方法返回的值应该相同;对象中御用equals方法比较标准的实例变量,都应该用于计算hashCode的值
  • 当把可变对象添加都HashSet之后,尽量不要去修改该集合元素中参与计算hashCode、equals的实例变量,否则可能导致这些元素无法正确操作

LinkedHashSet类:使用链表来维护元素的次序,这样是元素看起来是以插入顺序保存的;因为要维护插入的顺序,故性能低于HashSet;也不允许元素重复

TreeSet类:SortedSet接口的实现类;可以确保集合元素处于排序状态;采用红黑树的数据结构来存储集合元素;只能添加同一种类型的对象

  • 自然排序:调用集合元素的compareTo(Object obj) 方法来比较元素之间的大小,然后升序;此方法源自于Comparable接口
  • 定制排序:通过函数式接口Comparator的compare(T o1, T o2)比较o1和o2的大小确定排序
  • compareTo(Object obj) 比较相等,无法加入集合

EnumSet类:专为枚举涉及的集合类,也是有序的,按照Enum类内定义的顺序来决定集合的顺序;不允许加入null

各个Set实现类的性能分析:

  • 关于HashSet和TreeSet,只有当需要一个保持顺序的Set时,才应该使用TreeSet,否则使用HashSet
  • 对于普通的插入、删除操作,LinkedHashSet 比 HashSet 略微慢一点;这是额外链条带来的开销,好处是有了链表遍历LinkedHashSet会更快
  • EnumSet是所有实现类中性能最好的,使用比较局限,只能用于保存同一个枚举类的枚举值作为集合元素
  • HashSet、TreeSet和EnumSet都是线程不安全的。通过Collections工具类的synchronizedSortedSet 方法来“包装” 该Set集合

List集合:代表一个有序,可重复的集合,每个集合都有其对应的顺序索引;List集合默认按元素的添加顺序设置元素的索引

ArrayList和Vector 都是List的基于数组的实现类,使用initialCapacity来设置数组的长度,不指定默认长度为10;ArrayList线程不安全,Vector线程安全, 故ArrayList性能优于Vector;Arrays有一个asList的方法可以把一个数组或指定个数的对象转化为固定长度的List集合, 这个集合是Arrays的内部类ArrayList的实例

Queue集合:用于模拟队列,先进先出的容器;它有一个实现类PriorityQueue 保存的顺序是按照队列元素的大小重新排列的;出队时是按照最小元素先出队的

Deque接口代表一个双端队列,ArrayDeque是一个基于数组实现的的双端队列,可以指定一个numElements参数指定长度,不指定默认为16; 双端队列不仅可以当做队列,也可以当做栈来使用

LinkedList实现类内部是以链表的方式来存储的集合中的元素;而且还实现了Deque接口,故其既可以随机访问集合中的元素,也可以作为双端队列来使用

List实现类性能对比:ArrayList和ArrayQueue内部以数组实现,故随机访问元素性能更出色,遍历时使用随机访问方法(get)来遍历; LinkedList内部以链表实现,在插入删除元素性能出色,采用迭代器(Iterator)来遍历集合元素; Vector是比较原始的类,不做考虑;总体来说ArrayList性能更好,大多数情况使用ArrayList即可; 如果需要经常执行插入删除操作来改变包含大量数据的List集合,使用LinkedList

Map集合:用于保存一组键值对,key不允许重复,把Map集合的key放在一起看,就是一个Set集合,使用keySet可以得到一个Set集合;Java源码中, 是先实现了Map,然后包装一个value都为null的Map实现了Set集合

HashMap实现类:判断key的重复和HashSet一样,两个key通过equals 返回true同时HashCode也一样;使用containsValue判断是否包含置顶的value, 判断方式是equals方法返回true;HashMap允许key为null,但只能有一个,后面的后判断为重复;和HashSet一样,尽量不要把可变对象作为HashMap的key, 如果确实要这么做,也不要修改这个可变对象

LinkedHashMap,TreeMap和EnumMap和Set相关的实现类差不多,不在赘述;提供了一个操作集合的工具类Collections

泛型

由于集合会忘记进入集合元素的数据类型,编译时会变成Object类型,所以把不同类型的对象放入同一集合后,在使用时进行强制类型转换会引发异常; 所以引入了“参数化编程”的概念,即泛型,在创建集合的时候就指定集合元素的类型

所谓泛型,就是允许在定义类、接口、方法时使用类型形参,这个形参将在声明变量、创建对象、调用方法时动态指定(即传入的实际类型,也可以里类型实参); 当创建带泛型声明的自定义类,为该类定义构造器时,构造器还是原来的类名,不要增加泛型声明

Foo是Bar的子类(或者子接口),G是具有泛型声明的类或者接口,G<Foo> 不是G<Bar> 的子类型;类型通配符 eg: List<?>,指定类型通配符上限 eg: List<? extends Type>,指定通配符下限 eg: List<? super Type> ;通配符是用来支持灵活子类化的,泛型方法用来表示方法的一个参数或多个类型参数 之间的类型依赖关系,或者方法返回值和参数之间的类型依赖关系;将一个具有泛型信息的对象赋给另一个没有泛型信息的对象,尖括号内的类型信息将被扔掉

异常处理

Java把所有非正常的情况分为两种,一直是Error错误,一般是指与虚拟机相关的问题,如系统崩溃、虚拟机错误、动态链接失败等,这种错误无法恢复或不可捕获, 通常应用程序无法处理这些错误,故不应该试图使用catch块来捕获Error对象,也无须在throws 子句中声明方法可能抛出Error及其子类。另一种当然就是需要程序员 自己处理的Exception异常,下图为Java常见的异常类之间的继承关系: 关于finally:除非在try块或者catch块中调用了退出虚拟机的方法,否则finally块一定会被执行;同时如果finally中包含return或者throw,会导致try块、catch块中 的return和throw语句失效

异常处理规则:

  • 成功的异常处理需要实现4个目标:是程序代码混乱最小化、捕获并保留诊断信息、通知合适人员、采用合适的方式结束异常活动
  • 不要过度使用异常:异常只应该用于处理非正常情况,不要使用异常处理来代替正常的流程控制
  • 不要使用过于庞大的try块:应该把大块的try块分割成多个可能出现异常的程序段落,分别捕获和处理
  • 避免使用Catch All语句
  • 不要忽略捕获到的异常:要么处理,要么重新抛出,要么让上层调用者来负责处理异常

注解

Annotation 是一个接口,程序可以通过反射来获取指定的程序元素的 Annotation 对象,然后通过Annotation 对象来获取注解里的元数据

五个基本Annotation:

  • @Override 限定重写父类方法,指定此方法一定是重写的父类的方法,父类中无此方法,则编译报错(只能修饰方法)
  • @Deprecated 标示已过时(类,方法等),使用过时方法会引起编译器报错
  • SuppressWarnings 抑制编辑器警告,使用此修饰的程序元素(以及该程序元素中的所有子元素)会取消显示指定的编译器警告
  • SafeVarargs 抑制堆污染警告,当把一个不带泛型的对象赋给一个带泛型的变量,就会发生堆污染
  • FunctionalInterface 使用此注解修饰的接口,必须是函数式接口

六个元Annotation:

  • @Retention 只能用于修饰Annotation 定义,用于指定被修饰的Annotation 可以保留多长时间,其value的值只能为RetentionPolicy.CLASS(默认值,把Annotation记录在class中, 运行时JVM不可获取Annotation的信息),RetentionPolicy.RUNTIME(把Annotation记录在class中,运行时JVM可以获取Annotation的信息),RetentionPolicy.SOURCE (Annotation只保留在源代码中,编译器直接丢弃)
  • @Target 只能用于修饰Annotation 定义,用于指定被修饰的Annotation 能用于哪些程序单元
  • @Documented 指定被该元Annotation 修饰的 Annotation类将被javadoc工具提取成文档
  • @Inherited 指定被它修饰的Annotation 具有继承性;即此Annotation修饰的类的子类,也会被此Annotation修饰

自定义Annotation:使用@interface 关键字定义;其成员变量使用 default 指定其初始值;重复注解中,“容器”注解的保留期,必须比它嗦包含的注解保留期更长

IO

输入流:由InputStream(字节流)和Reader(字符流)作为基类;输出流:由OutputStream(字节流)和Writer(字符流)作为基类 如果进行输入/输出的内容是文本内容,则考虑使用字符流;如果是二进制内容,则考虑使用字节流;