Java基础
初识Java
特点:
- 一次编译,到处运行(半编译半解释型语言,先通过javac编译为字节码文件,再由JVM解释为机器码)
- 相比C++简单,没有指针语法,只有引用,更健壮,有垃圾回收机制
- 面向对象,不支持多重继承,使用接口概念进行实现
- 分布式,有丰富的例程库处理HTTP,FTP等TCP/IP协议
- 可移植性,Java的基本数据类型大小都做了明确说明,固定不变,不存在代码移植时平台不同而造成的错误
- 高性能,边解释边执行,垃圾回收,使用JIT即时编译器,热点代码探测提高执行效率
- 多线程, 支持并发程序设计
- 动态性,支持反射找到运行时类型信息
安装:
环境变量:
为什么配置环境变量? 观察发现在执行文件目录底下敲命令可以执行,而在其他目录下不可执行, 所以环境变量就是一个查找的路径, 可以告诉命令行要在哪里找。 如果环境变量修改了 一定要重启命令行 或者使用source命令。
Java程序
Java程序的运行
Java是一门半编译型、半解释型语言。
先通过javac编译程序把源文件进行编译,编译后生成的.class文件是由字节码组成的平台无关、面向JVM的文件。
最后启动java虚拟机来运行.class文件,此时JVM会将字节码转换成平台能够理解的形式来运行。
JDK里面就包含了javac和java工具,Java程序最终是在JVM(Java虚拟机)中运行的
JDK、JRE、JVM之间的关系?
- JDK(Java Development Kit): Java开发工具包,提供给Java程序员使用,包含了JRE,同时还包含了编译 器javac与自带的调试工具Jconsole、jstack等。
- JRE(Java Runtime Environment): Java运行时环境,包含了JVM,Java基础类库。是使用Java语言编写程 序运行的所需环境。
- JVM:Java虚拟机,运行.class字节码文件
程序编写规范
- 类名 -- 大驼峰命名 方法--小驼峰命名 对象名--小驼峰命名
JavaDoc
- Javadoc命令编译之后会自动生成网页 网页的内容根据 @params等注释解释
编码
- 编写代码的时候 要注意编码格式 utf-8还是gbk 可以在编译选项处加上 -encoding utf-8
- Java字符类型采用unicode编码方案 每个unicode码占用两个字节 两个字节--》16个比特位 也就是使用两个字节来表示一个字符 前128个字符是ASCII码 后面拓展码
- acsii码 与 unicode码不同 为单字节编码 只占用一个字节
数据类型与变量
基本数据类型
四类八种
四类: 整型,浮点型,字符型,布尔型
八种:
- Java中没有无符号类型 符号位不属于数值位
- 字面常量默认的类型: 整型int 浮点double 注意没有加f的小数都默认为double类型
- int 无论在何种系统上都是4个字节 设置初始值时,值不能超过int的表示范围,否则会导致溢出
- 在 Java 中, int 除以 int 的值仍然是 int(会直接舍弃小数部分)
- C 语言中使用 ASCII 表示字符, 而 Java 中使用 Unicode 表示字符. 因此一个字符占用两个字节, 表示的字符种类更多, 包括中文.
- Java 的 boolean 类型和 int 不能相互转换, 不存在 1 表示 true, 0 表示 false 这样的用法
类型转换:
自动类型转换(隐式)
数据范围小的转为数据范围大的时会自动进行。
强制类型转换(显式)
强制类型转换:当进行操作时,代码需要经过一定的格式处理,不能自动完成。特点:数据范围大的到数据范围小的,可能精度丢失,不相干的类型无法转换。
类型提升:
- int与long之间:int会被提升为long,不同类型的数据混合运算, 范围小的会提升成范围大的.
- byte与byte的运算,会先提升为int(比int小的都是)进行原酸,得到的结果是int,需要强制转换才能赋值给一个byte类型变量
原因:
由于计算机的 CPU 通常是按照 4 个字节为单位从内存中读写数据. 为了硬件上实现方便, 诸如 byte 和 short这种低于 4 个字节的类 型, 会先提升成 int, 再参与计算.
字符串类型:
- String类, 引用类型
- Java的String不会以'\0'结尾 底层就是一个数组
- String与基本数据类型的转换: String到基本数据类型-》对应包装类型的parse方法,基本数据类型到String-> String.valueOf()创建对象
- Java中 如果 字符串和整数 中有 + 为拼接符, 拼接字符串
- num+"" 为字符串
运算符
- 运算符之间是有优先级的. 具体的规则我们不必记忆. 在可能存在歧义的代码中加上括号即可.
基本操作符:
- Int/int -> 向下取整
- 两侧操作数类型不一致时,向类型大的提升 1+0.2 1会提升为double
增量操作符
该种类型运算符操作完成后,会将操纵的结果赋值给左操作数
int a = 4;
double d = 5.0;
a+=d; //对于+=操作符来说 自动给你进行转换
自增/减操作符
前置++ 先+1,然后使用变量+1之后的值,后置++ 先使用变量原来的值,表达式结束时给变量+1
逻辑运算符
&& 和 || 遵守短路求值的规则.
- 对于 && , 如果左侧表达式值为 false, 则表达式结果一定是 false, 无需计算右侧表达式.
- 对于 ||, 如果左侧表达式值为 true, 则表达式结果一定是 true, 无需计算右侧表达式.
位运算符
-
按位取反 ~: 如果该位为 0 则转为 1, 如果该位为 1 则转为 0
-
按位异或 ^: 如果两个数字的二进制位相同, 则结果为 0, 相异则结果为 1. 如果两个数相同,则异或的结果为0
应用: 找只出现了一次的元素 可以直接使用异或运算符 异或运算符中相同的元素进行运算结果为0 0 与 任何元素异或等于那个元素 所以一直异或下去就可以得到那个单独的值
-
无符号右移 >>>: 最右侧位不要了, 最左侧补 0
-
没有无符号左移这种运算符!!!
应用:
- 左移 1 位, 相当于原数字 * 2. 左移 N 位, 相当于原数字 * 2 的N次方.
- 右移 1 位, 相当于原数字 / 2. 右移 N 位, 相当于原数字 / 2 的N次方.
- 由于计算机计算移位效率高于计算乘除, 当某个代码正好乘除 2 的N次方的时候可以用移位运算代替
易错,三目运算符各部分遵循就近原则,所以第一个?前面就是表达式部分,第一个:后面的整体就是条件表达式为false时对应的部分,那么这个表达式的值实际上为false
程序逻辑控制
Switch
switch的括号内只能是以下类型的表达式:
- 基本类型:byte、char、short、int,注意不能是long类型
- 引用类型:String常量串、枚举类型
方法
- 在Java中,实参的值永远都是拷贝到形参中,形参和实参本质是两个实体
- 对于基础类型来说, 形参相当于实参的拷贝. 即 传值调用、
- 在参数列表加上... 表示可变参数 int...a 与 int[]数组类型等价
解决Swap交换问题
传引用类型参数 (例如数组来解决这个问题),数组中的位置互换
方法重载
如果多个方法的名字相同,参数列表不同,则称该几种方法被重载了。与返回值类型是否相同无关
方法签名:
经过编译器编译修改过之后方法最终的名字。具体方式:方法全路径名+参数列表+返回值类型,构成方法完整的名字。
数组(以[开头,配合其他的特殊字符,表述对应数据类型的数组,几个[表述几维数组)
引用类型,以L开头,以;结尾,中间是引用类型的全类名
递归
- 将原问题划分成其子问题,注意:子问题必须要与原问题的解法相同 2. 确定递归出口
- 方法调用的时候, 会有一个 "栈" 这样的内存空间描述当前的调用关系. 称为调用栈. 每一次的方法调用就称为一个 "栈帧", 每个栈帧中包含了这次调用的参数是哪些, 返回到哪里继续执行等信息
- 递归对空间的要求比较高
- 迭代--》循环
数组
创建方法
T[] 数组名 = new T[N];
初始化
动态初始化
int[] array = new int[10];
静态初始化
T[] 数组名称 = {data1, data2, data3, ..., datan};
如果数组中存储元素类型为基类类型,默认值为基类类型对应的默认值
- 在数组中可以通过 数组对象.length 来获取数组的长度
数组是引用类型
JVM的内存分布:
Java虚拟机栈,堆,Java本地方法栈,方法区,程序计数器
- 虚拟机栈(JVM Stack): 与方法调用相关的一些信息,每个方法在执行时,都会先创建一个栈帧,栈帧中包含 有:局部变量表、操作数栈、动态链接、返回地址以及其他的一些信息,保存的都是与方法执行时相关的一 些信息。比如:局部变量。当方法运行结束后,栈帧就被销毁了,即栈帧中保存的数据也被销毁了。
- 堆(Heap): JVM所管理的最大内存区域. 使用 new 创建的对象都是在堆上保存 (例如前面的 new int[]{1, 2, 3} ),堆是随着程序开始运行时而创建,随着程序的退出而销毁,堆中的数据只要还有在使用,就不会被销 毁。
- 方法区(Method Area): 用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数 据. 方法编译出的的字节码就是保存在这个区域
引用数据类型
- 引用变量并不直接存储对象本身,可以简单理解成存储的是对象在堆中空间的起始地址。通过该地址,引用变量便可以去操作对象
- 当一个对象没有引用指向的时候就会被系统自动回收
- null 在 Java 中表示 "空引用" , 也就是一个不指向对象的引用.一旦尝试读写, 就会抛出 NullPointerException
- 如果将引用传入方法之后又重新开辟内存 那么这个参数不再引用原来的空间 此时引用的是新的空间 方法销毁时 不会改变原来形参的值
- 引用可以避免对整个数组的拷贝(数组可能比较长, 那么拷贝开销就会很大).
数组转字符串
Arrays工具累 在java.util下
Arrays.toString() 打印数组
Arrays.sort() 排序数组
Arrays.equals 判断两个数组的内容是否一致
Arrays.fill(arr,9) 将数组里面所有的元素都填充为9
Arrays.copyOf(arr1,arr1.length) 可以拷贝 也可以进行扩容操作 第二个参数为指定的数组长度
copyOfRange 指定范围进行拷贝
System.arraycopy(原数组, 原数组下标, 目的数组,目的数组的位置,拷贝的长度) 是native方法(底层是由C,C++实现, 特点是速度很快) 将引用赋值给别的数据类型不是拷贝
二维数组
二维数组本质上也就是一维数组, 只不过每个元素又是一个一维数组.
数据类型 数组名称 = new 数据类型 [行数][列数] { 初始化数据 }
Java当中的二维数组(2x3) 真正是两个元素为引用类型 指向了两个不同的一维数组(不同的行)
int arr = new[2][]; 只指定了为两个引用类型的数组 但引用类型没有指向的对象 所以两个引用变量的值都为null
可以分别初始化和赋值 所以这个数组是可以不规则的
类与对象
- 面向对象(OOP)-》 主要依靠对象之间的交互完成一件事情
- 类对一个实体(对象)进行描述
类的定义
- 大驼峰命名类
class className{
fields;
methods;
}
- 用new关键字实例化对象
this引用
this引用指向当前对象(成员方法运行时调用该成员方法的对象),在成员方法中所有成员变量的操作,都是通过该 引用去访问。编译器自动完成传递this引用
- this的类型:对应类类型引用,即哪个对象调用就是哪个对象的引用类型
- this只能在"成员方法"中使用
- 在"成员方法"中,this只能引用当前对象,不能再引用其他对象
构造方法
- 名字必须与类名相同
- 没有返回值类型,设置为void也不行
- 创建对象时由编译器自动调用,并且在对象的生命周期内只调用一次(相当于人的出生,每个人只能出生一次)
- 构造方法可以重载(用户根据自己的需求提供不同参数的构造方法)
- 编译器会默认生成一个不带参数的构造方法。如果有构造方法,那么编译器将不会再生成不带参数的构造方法
-
构造方法中,可以通过this调用其他构造方法来简化代码,比如无参构造可以讲默认值传递到有参构造的方法中
-
this(...)必须是构造方法中的第一条语句,用this调用构造方法也只能在构造方法中出现
原因: this代表的是当前对象的引用,只有在调用构造完成之后才产生了对象,所以this代表的是当前对象的引用(new产生)
-
绝大多数情况下使用public来修饰,特殊场景下会被private修饰(单例模式)
new
当new一个对象时,JVM层面发生了
- 检测对象对应的类是否加载了,如果没有加载则加载
- 为对象分配内存空间
- 处理并发安全问题(两个对象同时创建,要看分配的内存空间是否会冲突)
- 初始化所分配的空间
- 设置对象头信息
- 调用构造方法,给对象中各个成员赋值
面向对象
- 三大特性: 封装,继承, 多态
- 封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行 交互
- Java中主要通过类和访问权限来实现封装:类可以将数据以及封装数据的方法结合在一起,访问权限用来控制方法或者字段能否直接在类外使用
- default 什么都不写时的默认权限
- 一般情况下成员变量设置为private,成员方法设置为public。
包
-
为了更好的管理类,把多个类收集在一起成为一组,称为软件包。有点类似于目录。
-
包是对类、接口等的封装机制的体现,是一种对类或者接口等的很好的组织方式,比如一个包中的类不想被其他包中的类使用。
-
导包: import java.util.*; *为通配符,表示该包下的所有类都可以使用, 但最好还是显式指定包中的哪个类,避免不同包下的类重名造成冲突, 用的时候才会导入 用哪个拿哪个 不会像C++全部导入,import 更类似于 C++ 的 namespace 和 using,比如写全类名的时候也是可以正常运行的
-
自定义包:
- 在文件的最上方加上一个 package 语句指定该代码在哪个包中.
- 包名需要尽量指定成唯一的名字, 通常会用公司的域名的颠倒形式(例如 com.bit.demo1 )
- 如果一个类没有 package 语句, 则该类被放到一个默认包中
-
常见包;
- java.lang:系统常用基础类(String、Object),此包从JDK1.1后自动导入。
- java.lang.reflect:java 反射编程包;
- java.net:进行网络编程开发包。
- java.sql:进行数据库开发的支持包。
- java.util:是java提供的工具程序包。(集合类等) 非常重要
- java.io:I/O编程开发包
static
- 静态成员变量 --》 类变量
- 静态成员方法 --》 类方法
成员:
在Java中,被static修饰的成员,称之为静态成员,也可以称为类成员,其不属于某个具体的对象,是所有对象所共享的。
- 可以通过类名进行访问
- 存储在方法区当中
方法:
Java中,被static修饰的成员方法称为静态成员方法,是类的方法,不是某个对象所特有的。静态成员一般是通过静态方法来访问的。
- 静态方法当中不能调用非静态的成员变量或者是非静态的成员方法, 因为其他的成员变量和方法都依赖实例化创建,不包含this 因为对象都没有创建
- 可以通过对象调用,也可以通过类名.静态方法名(...)方式调用,更推荐使用后者
静态成员通过静态代码块进行初始化
普通代码块 构造块 静态块 同步代码块(后续讲解多线程部分再谈)
- 构造块(实例代码块):在类中,成员方法外
{
this.name = "bit";
this.age = 12;
this.sex = "man";
System.out.println("I am instance init()!");
}
2. 静态代码块: 初始化静态成员变量
static{
classRoom = "bit306";
System.out.println("I am static init()!");
}
- 静态代码块不管生成多少个对象,其只会执行一次
- 静态成员变量是类的属性,因此是在JVM加载类时开辟空间并初始化的
- 如果一个类中包含多个静态代码块,在编译代码时,编译器会按照定义的先后次序依次执行(合并)
- 实例代码块只有在创建对象时才会执行
- 静态代码块先执行 然后才是实例代码块 然后才是构造方法
内部类
在 Java 中,可以将一个类定义在另一个类或者一个方法的内部,前者称为内部类,后者称为外部类。内部类也是封装的一种体现。
public class OutClass {
class InnerClass{
}
}
- 内部类和外部类共用同一个java源文件,但是经过编译之后,内部类会形成单独的字节码文件
实例内部类
- 外部类中的任何成员都可以在实例内部类方法中直接访问
- 实例内部类所处的位置与外部类成员位置相同,因此也受public、private等访问限定符的约束
- 在实例内部类方法中访问同名的成员时,优先访问自己的,如果要访问外部类同名的成员,必须:外部类名 称.this.同名成员 来访问
- 实例内部类对象必须在先有外部类对象前提下才能创建
- 实例内部类的非静态方法中包含了一个指向外部类对象的引用
- 外部类中,不能直接访问实例内部类中的成员,如果要访问必须先要创建内部类的对象
静态内部类
被static修饰的内部成员类称为静态内部类
在静态内部类中只能访问外部类中的静态成员, 如果实在要访问,实例化外部类对象再进行访问
创建静态内部类对象时,不需要先创建外部类对象
匿名内部类
直接new 实现一个接口 然后重写接口里面的方法 还可以直接在new对象之后直接调用
对象的打印
重写toString()方法即可 默认Object类中的toString方法输出对象的地址
继承和多态
继承
- Java中只存在单继承, 不存在多继承, 尽量不要超过三层的继承关系
借助extends关键字 子类定义时 extends 父类
继承是一种思想 对共性进行抽取 从而实现了代码的复用
继承之后,子类可以复用父类中成员,子类在实现时只需关心自己新增加的成员即可。
- 子类会将父类中的成员变量或者成员方法继承到子类中了
- 子类继承父类之后,必须要新添加自己特有的成员,体现出与基类的不同,否则就没有必要继承了
- 如果访问的成员变量子类中有,优先访问自己的成员变量。(就近原则)
- 如果访问的成员变量子类中无,则访问父类继承下来的,如果父类也没有定义,则编译报错。
- 如果访问的成员变量与父类中成员变量同名,则优先访问自己的。
- 区分相同名称的成员是子类的(this.) 还是父类的(super.)
通过派生类对象访问父类与子类同名方法时,如果父类和子类同名方法的参数列表不同(重载),根据调用 方法适传递的参数选择合适的方法访问
super关键字的使用
- 只能在非静态方法中使用
- 在子类方法中,访问父类的成员变量和方法。
- 子类对象构造时,需要先调用基类构造方法,然后执行子类的构造方法
- 默认会先调用super() --》父类的无参构造 父类的成员需要初始化 并不会生成父类对象
- super(...)不能与this(...)同时使用
super与this的比较
- this是当前对象的引用,当前对象即调用实例方法的对象,super相当于是子类对象中从父类继承下来部分成 员的引用
- 在非静态成员方法中,this用来访问本类的方法和属性,super用来访问父类继承下来的方法和属性
- 在构造方法中:this(...)用于调用本类构造方法,super(...)用于调用父类构造方法,两种调用不能同时在构造 方法中出现
- 构造方法中一定会存在super(...)的调用,用户没有写编译器也会增加,但是this(...)用户不写则没有
父类子类初始化顺序
静态代码块是在类加载阶段就执行, 所以一定是比其他阶段都要早的, 所以是父类与子类的静态代码块先执行, 然后才是父类的实例代码块先执行,默认第一句super(...),然后就到父类的构造方法执行, 然后才是子类的实例代码块和构造方法执行
protected
主要用于继承关系上
通过super进行访问, 不同包中父类的成员变量, 要在非static方法内进行访问
修饰词选择:
类要尽量做到 "封装", 即隐藏内部实现细节, 只暴露出 必要 的信息给类的调用者
我们在使用的时候应该尽可能的使用 比较严格 的访问权限. 例如如果一个方法能用 private, 就尽量不要用public
final
修饰变量
修饰成员方法:
修饰类: 表示该类不可以再被继承,比如String
修饰变量或者字段: 表示常量,不可修改
修饰方法: 表示该方法不能被重写
继承与组合
继承表示 is-a 组合表示has-a
一般来说, 能用组合尽量还是用组合, 组合用的更多
组合可以面向接口编程,也就是可以在运行时才决定对象内部的字段,是一种松耦合的设计,而继承是一种紧耦合的设计(子类依赖父类实现)
一个判断方法是,问一问自己是否需要从新类向基类进行向上转型。如果是必须的,则继承是必要的。反之则应该好好考虑是否需要继承。(需要两个类确实存在is-a的关系)
多态
- 具体点就是去完成某个行为,当不同的对象去完成时会产生出不同 的状态。
同一件事情,发生在不同对象身上就会产生不同的结果
条件
- 必须在继承体系下(才能发生向上转型)
- 子类必须要对父类中方法进行重写
- 通过父类的引用调用重写的方法
完成以上三个部分 就会发生动态绑定,动态绑定是实现多态的基础, 在代码运行时,当传递不同类对象时,会调用对应类中的方法。
- 子类对象 给 父类引用赋值 --》 向上转型(小范围自动提升为大范围)
- 父类对象 给 子类 --》 向下转型(大范围强制转换为小范围),因为一个父类可能会有许多的子类,如果转换失败,就会导致程序不安全, 因此通常搭配instanceof使用,来确定该对象是不是具体子类的实例, 如果是才进行转换
- 通过父类引用 只能调用父类自己特有的成员方法或者成员变量
常见的可以发生向上转型的时机 :
- 直接赋值(将子类对象赋值给父类引用)
- 方法的参数 , 传参的时候进行向上转型(形式参数是父类,传递的实际参数是子类)
- 返回值 向上转型(返回的是子类 但又赋值给父类引用)
重写(在继承关系里)
- 方法的返回值一样
- 方法名一样
- 方法的参数列表一样
- 用@override注解修饰,可以进行合法性校验
- 设计原则: 对于已经投入使用的类,尽量不要进行修改。最好的方式是:重新定义一个新的类,来重复利用其中共性的内容, 并且添加或者改动新的内容。
- 重写eat方法之后 变成了调用子类的eat, 这个过程就叫做动态绑定(程序运行的时候才会选择具体的对象的具体方法)
- 此时 a这个引用调用 eat方法可能会有多种不同的表现(和 a 引用的实例相关), 这种行为就称为 多态.
静态绑定:也称为前期绑定(早绑定),即在编译时,根据用户所传递实参类型就确定了具体调用那个方法。典型代 表函数重载。(会根据参数类型与个数进行匹配,编译时期发生) 动态绑定:也称为后期绑定(晚绑定),即在编译时,不能确定方法的行为,需要等到程序运行时,才能够确定具体 调用那个类的方法。(运行时期发生)
多态的优点: 避免大量if-else语句,代码可拓展性变强
避免在构造方法中调用重写方法
父类中如果调用了一个重写的方法, 子类的实例尚未创建,可能会造成程序异常
所以在构造函数内,尽量避免使用实例方法,除了final和private方法。因为这两个修饰词修饰的方法无法被继承
尽量不要在构造器中调用方法(如果这个方法被子类重写, 就会触发动态绑定, 但是此时子类对象还没构造完成), 可能会出现一些隐藏的但是又极难发现的问题
抽象类和接口
抽象类
- 使用abstract修饰的方法 叫做抽象方法,没有方法体
- 使用abstract修饰的类 叫做抽象类,只要包含抽象方法的类必须都是抽象类
- 抽象类不可以进行实例化 不能描述一个对象(不是一个具体的类,信息不够)
- 抽象类除此之外与普通类没有区别 一样可以定义成员变量 成员方法(可以被继承来实例化和调用)
- 当一个普通类 继承 抽象类 需要重写所有的抽象方法(因为抽象方法没有方法体)
- 抽象类的出现 就是为了被继承
- abstarct 和 final 不能同时出现 相互矛盾 一个要求必须被继承 一个严禁被继承
- 抽象方法不能是 private 的 不然将无法被继承之后重写,那么这个方法将毫无意义
- 抽象类中不一定包含抽象方法,但是有抽象方法的类一定是抽象类
- 抽象类中可以有构造方法,供子类创建对象时,初始化父类的成员变量
使用抽象类相当于多了一层编译器的校验,实际工作的处理应该由子类完成
接口
接口其实就是公共的行为规范标准,实现的时候只要符合规范标准就可以通用
在Java中,接口事多个类的公共规范,是一种引用数据类型,是一种标准
接口的定义格式与定义类的格式基本相同,将class关键字换成 interface 关键字,就定义了一个接口。
接口的命名规范
- 接口的命名一般以大写字母I开头以区分接口
- 接口的命名一般使用形容词词性的单词,以表示特定的功能标准
- 阿里编码规范中约定, 接口中的方法和属性不要加任何修饰符号, 保持代码的简洁性. 直接就用默认的public abstract修饰即可,编译器会自动加上
- 默认成员变量都是public static final修饰的 意味着是一个常量
- 接口不能进行实例化(是抽象类的进一步抽象)
- 类 和 接口之间的关系 使用 implement实现接口 必须实现接口当中的所有方法(重写)
- 与普通类一样,也可以类向上转型为接口,从而发生动态绑定
- 接口也是有对应的字节码文件的
- 重写接口中方法时,不能使用默认的访问权限(接口中方法的权限是public, 子类的访问权限必须要大于父类,否则动态绑定会发生错误,所以重写的方法访问权限只能是public)
- 用static和default(注意接口中的方法编译器会默认加public abstract修饰符,所以此处要强调default修饰,default关键字要写上去)修饰的时候 接口中可以实现方法, static是可以直接通过接口类型调用方法,default修饰的方法实现的类可以调用,但如果出现同时实现的两个接口中有同名方法,则必须对方法体进行重写
- 接口中不能有静态代码块和构造方法, 静态变量为常量不可改变,接口不可以实例化对象,构造方法无意义
- 类没有实现接口中的所有抽象方法的话就只能是抽象类
修饰顺序: 定义类时,先是继承extends 再是实现implements 可以实现多个接口(以此解决Java不可多重继承的问题)
Java 面向对象编程中最常见的用法: 一个类继承一个父类, 同时实现多种接口. 时刻牢记多态的好处, 让程序猿忘记类型. 有了接口之后, 类的使用者就不必关注具体类型,而只关注某个类是否具备某种能力
接口之间的继承
接口可以继承接口,达到复用的效果,相当于把多个接口合并在一起, 接口是可以多继承的
比较接口
CompareTo 类里面实现,对类的侵入性强,一旦写好了,就只能用这种方式比较
Comparator 内部类,临时实现比较的规则,灵活比较,传入两个要比较的对象即可
一般情况下 自定义的类型 一定是可以进行比较的 最基本的一个功能
Arrays.sort的两个重载方法,一个参数为数组, 一个参数为数组+比较器接口类(自己临时实现Comparator接口,指定比较规则)
Clonable接口,深拷贝
Clonable接口与Clone方法
- Object 类中存在一个 clone 方法, 调用这个方法可以创建一个对象的 "拷贝". 但是要想合法调用 clone 方法, 必须要先实现 Clonable 接口, 否则会抛出CloneNotSupportedException异常
- Object类里面的clone方法 是protected限定符修饰 Object位于java.lang包中 而自定义类型在自己的包当中 自定义类型继承Object类(属于类外继承 所以访问的限定符为protected), 所以这里需要super.方法才能进行调用(protected修饰的方法或者成员是无法直接访问的)
- native修饰 --》 clone方法的底层由C/C++ 实现 可能会抛出异常(不支持克隆异常)
- clone方法返回值为Object 调用成功返回结果需要自己进行强制转换为自定义类型
- 克隆是创建了一个新的对象,复制内容,并对引用进行返回
- 自定义类型需要支持clonable接口才能够成功调用克隆方法而不发生异常
- Clonable 接口 的 内容是空的 --》标记接口/空接口 --》 作用: 表明当前类是可以被克隆的,由此使用clone方法时才可以被调用
- 实现自定义类型可以被克隆调用 --》 在自定义类中重写Object类中的clone方法 在方法当中直接用super关键字调用Object类的clone方法 返回强制转换后的自定义类型对象即可
浅拷贝
- Clonable拷贝的对象是一份浅拷贝,只拷贝了一层引用(不会把引用指向的对象空间也进行拷贝)
深拷贝
- 深拷贝 --》把对象里面引用的空间也复制一份
- 要实现深拷贝 里面的引用的自定义类型也要实现Clonable接口 然后重写Clone方法
典型易错题,传递给方法的参数是一份拷贝,所以过程是str接受了ex.str的拷贝,然后将形式参数str引用的空间修改为"test ok"引用的空间,但是在方法结束之后这个str形式参数被销毁,而原来的ex.str未受到影响
抽象类与接口的区别(重点)
- 抽象类可以包含普通方法与普通字段(可以被子类继承使用),其与普通类的区别仅仅是包含了空方法体的抽象方法,子类必须重写所有的抽象方法
- 接口 是抽象方法+全局常量的组合使用 权限只能是public
Object
- Java中的顶级父类,所有引用类型默认继承Object类
toString方法
- 打印类的信息,默认打印对象地址
equals方法
- Object中默认是按照地址比较的,后续可以自己重写比较的规则
hashcode方法
- 底层是由C/C++代码写的,也是native方法,速度很快,返回值是个内存地址
- 所以两个对象的默认的hash值是不一样的, 当然也可以重写hashcode方法,设定获取成员字段集合的hash值,由此便可以通过hash值来比较对象的内容是否是一致的
- 事实上hashCode() 在散列表中才有用,在其它情况下没用。在散列表中hashCode() 的作用是获取对象的散列码,进而确定该对象在散列表中的位置。
阶段项目
思路: 将增删改查操作看为对象,统一用一套规范(接口),对图书集合进行操作
String类
- Java中字符串为单独的String类
- 面试常问,重点内容,StringBuilder,StringBuffer,String的区别,String不可变,StringBuilder和StringBuffer可变,但是StringBuffer是线程安全的
String
-
构造:1. 常量串直接赋值引用 2.构造方法传入常量串 3. 用字符数组构造
-
“”字面常量串也是String类型
-
引用类型,不存储字符串本身,引用的空间内部有一个value字符数组,一个hash值
-
== 比较的是字符串的地址, equals方法比较的是字符串的内容
-
compareTo按照字典序比较,如果前面都相同,最后比较长度,compareToIgnoseCase忽略大小写进行比较
-
字符串查找 --》KMP算法(挖坑)实现,相关方法:charAt(int),indexOf(Char/String),lastIndexOf(Char/String)
-
字符串转化(基本类型与字符串--》xxx.parsexxx,String.valueOf(xxx), 大写 toUpperCase, 小写转toLowerCase, 数组toxxxArray, 格式化String.format)
-
字符串替换 replaceAll/First(regex,replacement)
-
拆分spilt(regex)
-
字符"|","*","+"都得加上转义字符,前面加上 "\" .
- 而如果是 "" ,那么就得写成 "\\" .
- 如果一个字符串中有多个分隔符,可以用"|"作为连字符.
-
-
字符串截取substring(前闭后开)
-
trim() 去掉左右空格
字符串不可变
-
String被final修饰不可继承
-
private修饰value字符数组 外部不可修改,final修饰表面引用的字符数组地址不可变
-
因为字符串不可变,所以所有修改字符串内容的操作都是新建一个新的String对象
-
为什么 String 要设计成不可变的?(不可变对象的好处是什么?) (选学)
- 方便实现字符串对象池. 如果 String 可变, 那么对象池就需要考虑写时拷贝的问题了.
- 不可变对象是线程安全的.(不必考虑多线程同时进行对象操作的不一致性)
- 不可变对象更方便缓存 hash code, 作为 key 时可以更高效的保存到 HashMap 中.
字符串修改的高效实现
- 直接对String进行大量修改会产生大量新的对象,每次创建和销毁对象都涉及大量的资源浪费,(比如+=操作,每次都创建一个新的StringBuilder对象append之后返回toString), 千万不要在IO内每次都对String操作!!!
- 我们在对String操作之前用String初始化一个StringBuilder对象,每次操作都操作这个StringBuilder对象,那么最后我们就只创建了一个StringBuilder,不会涉及大量的资源浪费
- StringBuffer的方法都有sychronized修饰,线程安全,同一时刻只有一个线程可以对对象进行操作,但如果只是单一线程操作,就像上厕所,每次开锁和上锁都会造成资源消耗,那么时间效率也不如StringBuilder
异常
异常体系结构:
- Throwable:是异常体系的顶层类,其派生出两个重要的子类, Error 和 Exception
- Error:指的是Java虚拟机无法解决的严重问题,比如:JVM的内部错误、资源耗尽等,典型代表:StackOverflowError和OutOfMemoryError(栈溢出错误,内存耗尽错误),一旦发生回力乏术。
- Exception:异常产生后程序员可以通过代码进行处理,使程序继续执行。比如:感冒、发烧。我们平时所说的异常就是Exception。
编译时异常--》受检查异常
运行时异常--〉RuntimeException及其所有子类
事后认错型处理异常:EAFP(遇到问题再处理) throw、try、catch、final、throws
异常抛出之后 后面的代码不会执行
throws :当前方法不处理异常,提醒方法的调用者处理异常, 调用声明抛出异常的方法时,调用者必须对该异常进行处理,或者继续使用throws抛出
try{
// 将可能出现异常的代码放在这里
}catch(要捕获的异常类型 e){
// 如果try中的代码抛出异常了,此处catch捕获时异常类型与try中抛出的异常类型一致时,或者是try中抛出异常的基类
时,就会被捕获到
// 对异常就可以正常处理,处理完成后,跳出try-catch结构,继续执行后序代码
}[catch(异常类型 e){//多个异常也可以用|符号分隔
// 对异常进行处理
}finally{
// 此处代码一定会被执行到
}]
如果抛出异常类型与catch时异常类型不匹配,即异常不会被成功捕获,也就不会被处理,继续往外抛,直到JVM收到后中断程序----异常是按照类型来捕获的
如果异常之间具有父子关系,一定是子类异常在前catch,父类异常在后catch,这样也能起到殿后的作用
finally
不管try catch语句是否捕获到异常,最后一定会执行finally里面的代码
应用: 多用于程序打开资源的释放,防止资源泄漏,比如文件,网络,数据库连接
也可以不用自己手写资源的close,可以采用try()里面打开资源的方式,执行完语句之后会自动释放资源
try(Scanner sc = new Scanner(System.in)){
int a = sc.nextInt();
}catch(RuntimeException ex){
ex...
}
finally无论try是否成功,或者在try中发生了返回语句,都会执行,有效防止了资源泄漏
自定义异常类
自定义的业务异常 要确定是否需要进行编译时检查(是否为受查异常),从而确定是继承Exception(编译时异常)还是RuntimeException(运行时异常)
另外,自定义异常时要实现一个带有String参数的构造方法,阐述异常产生的原因