抽象类和接口
抽象类
抽象类语法
// 抽象类:被abstract修饰的类 public abstract class A { // 抽象方法:被abstract修饰的方法,没有方法体 abstract public void fun(); abstract void func(); // 抽象类也是类,也可以增加普通方法和属性 public double funcc(){ return m; } protected double f; }
抽象类也是类,内部可以包含普通方法和属性,甚至构造方法
抽象类特性
- 抽象类不能直接实例化对象
- 抽象方法不能是 private 的
- 抽象方法不能被final和static修饰,因为抽象方法要被子类重写
- 抽象类必须被继承,并且继承后子类要重写父类中的抽象方法,否则子类也是抽象类,必须要使用 abstract 修饰
- 抽象类中不一定包含抽象方法,但是有抽象方法的类一定是抽象类
- 抽象类中可以有构造方法,供子类创建对象时,初始化父类的成员变量
抽象类作用
抽象类本身不能被实例化, 要想使用, 只能创建该抽象类的子类, 然后让子类重写抽象类中的抽象方法,使用抽象类相当于多了一重编译器的校验。
接口
接口语法
public interface 接口名称,接口名称...{ //可以实现多个接口 // 抽象方法 public abstract void method1(); // public abstract 是固定搭配,可以不写 public void method2(); abstract void method3(); void method4(); // 注意:在接口中上述写法都是抽象方法,少写固定搭配让代码更简洁 }
接口使用
接口不能直接使用,必须要有一个"实现类"来"实现"该接口,实现接口中的所有抽象方法。
注意:子类和父类之间是extends 继承关系,类与接口之间是 implements 实现关系。
接口间的继承
类和类之间是单继承的,一个类可以实现多个接口,接口与接口之间可以多继承。即:用接口可以达到多继承的目的。
接口可以继承一个接口, 达到复用的效果. 使用 extends 关键字。
接口间的继承相当于把多个接口合并在一起,不需要去实现接口中的方法。
接口特性
- 接口类型是一种引用类型,但是不能直接new接口的对象
- 接口中每一个方法都是public的抽象方法, 即接口中的方法会被隐式的指定为 public abstract(只能是public abstract,其他修饰符都会报错)
- 接口中的方法是不能在接口中实现的,只能由实现接口的类来实现
- 重写接口中方法时,不能使用默认的访问权限 (因为接口中的方法默认为 public abstract)
- 接口中可以含有变量,但是接口中的变量会被隐式的指定为 public static final 变量
- 接口中不能有静态代码块和构造方法
- 接口虽然不是类,但是接口编译完成后字节码文件的后缀格式也是.class
- 如果类没有实现接口中的所有的抽象方法,则类必须设置为抽象类
- jdk8中:接口中还可以包含default方法
注意 :
一个类可以实现多个接口 (继承是只能继承一个)
一个类实现多个接口时,每个接口中的抽象方法都要实现,否则类必须设置为抽象类。( IDEA 中使用 ctrl + i 快速实现接口)
抽象类和接口的区别
核心区别:
抽象类中可以包含普通方法和普通字段, 这样的普通方法和字段可以被子类直接使用(不必重写), 而接口中不能包含普通方法, 子类必须重写所有的抽象方法.
String
字符串构造的常用三种方法
public static void main(String[] args) { // 使用常量串构造 String s1 = "hello world"; System.out.println(s1); // 直接newString对象 String s2 = new String("hello world"); System.out.println(s1); // 使用字符数组进行构造 char[] array = {'h','e','l','l','o','w','o','r','l','d'}; String s3 = new String(array); System.out.println(s1); }
String对象的比较
1.比较是否引用同一个对象
对于内置类型,== 比较的是变量中的值;对于引用类型,比较的是引用中的地址。
- boolean equals(Object anObject) 方法:按照字典序比较(字符大小的顺序)
String类重写了父类Object中equals方法,Object中equals默认按照==比较,String重写equals方法后,按照如下规则进行比较,比如: s1.equals(s2)
public boolean equals(Object anObject) { // 1. 先检测this 和 anObject 是否为同一个对象比较,如果是返回true if (this == anObject) { return true; } // 2. 检测anObject是否为String类型的对象,如果是继续比较,否则返回false if (anObject instanceof String) { // 将anObject向下转型为String类型对象 String anotherString = (String)anObject; int n = value.length; // 3. this和anObject两个字符串的长度是否相同,是继续比较,否则返回false if (n == anotherString.value.length) { char v1[] = value; char v2[] = anotherString.value; int i = 0; // 4. 按照字典序,从前往后逐个字符进行比较 while (n-- != 0) { if (v1[i] != v2[i]) return false; i++; } return true; } } return false; }
- int compareTo(String s) 方法: 按照字典序进行比较
与equals不同的是,equals返回的是boolean类型,而compareTo返回的是int类型。具体比较方式:
- 先按照字典次序大小比较,如果出现不等的字符,直接返回这两个字符的大小差值
- 如果前k个字符相等(k为两个字符长度最小值),返回值两个字符串长度差值
public static void main(String[] args) { String s1 = new String("abc"); String s2 = new String("ac"); String s3 = new String("abc"); String s4 = new String("abcdef"); System.out.println(s1.compareTo(s2)); // 不同输出字符差值-1 System.out.println(s1.compareTo(s3)); // 相同输出 0 System.out.println(s1.compareTo(s4)); // 前k个字符完全相同,输出长度差值 -3 }
- int compareToIgnoreCase(String str) 方法:与compareTo方式相同,但是忽略大小写比较
public static void main(String[] args) { String s1 = new String("abc"); String s2 = new String("ac"); String s3 = new String("ABc"); String s4 = new String("abcdef"); System.out.println(s1.compareToIgnoreCase(s2)); // 不同输出字符差值-1 System.out.println(s1.compareToIgnoreCase(s3)); // 相同输出 0 System.out.println(s1.compareToIgnoreCase(s4)); // 前k个字符完全相同,输出长度差值 -3 }
StringBuilder和StringBuffer
由于String的不可更改特性,为了方便字符串的修改,Java中又提供StringBuilder和StringBuffer类。这两个类大部分功能是相同的.
String和StringBuilder最大的区别在于String的内容无法修改,而StringBuilder的内容可以修改。频繁修改字符串的情况考虑使用StringBuilder.
注意:String和StringBuilder类不能直接转换。如果要想互相转换,可以采用如下原则:
String变为StringBuilder: 利用StringBuilder的构造方法或append()方法
StringBuilder变为String: 调用toString()方法
String、StringBuffer、StringBuilder的区别:
String的内容不可修改,StringBuffer与StringBuilder的内容可以修改.
StringBuffer与StringBuilder大部分功能是相似的
StringBuffer采用同步处理,属于线程安全操作;而StringBuilder未采用同步处理,属于线程不安全操作
异常
异常的体系结构
异常种类繁多,为了对不同异常或者错误进行很好的分类管理,Java内部维护了一个异常的体系结构:
- Throwable:是异常体系的顶层类,其派生出两个重要的子类, Error 和 Exception
- Error:指的是Java虚拟机无法解决的严重问题,比如:JVM的内部错误、资源耗尽等,典型代表:
- StackOverflowError和OutOfMemoryError,一旦发生回力乏术。
- Exception:异常产生后程序员可以通过代码进行处理,使程序继续执行。比如:感冒、发烧。我们平时所说的异常就是Exception。
异常分类
- 编译时异常
在程序编译期间发生的异常,称为编译时异常,也称为受查异常
- 运行时异常
在程序执行期间发生的异常,称为运行时异常,也称为非受查异常
RunTimeException以及其子类对应的异常,都称为运行时异常。比如:NullPointerException、ArrayIndexOutOfBoundsException、ArithmeticException。
异常抛出
借助throw关键字,抛出一个指定的异常对象,将错误信息告知给调用者。
throw new XXXException("异常产生的原因");
注意:
- throw必须写在方法体内部
- 抛出的对象必须是Exception 或者 Exception 的子类对象
- 如果抛出的是 RunTimeException 或者 RunTimeException 的子类,则可以不用处理,直接交给JVM来处理
- 如果抛出的是编译时异常,用户必须处理,否则无法通过编译
- 异常一旦抛出,其后的代码就不会执行
异常的捕获
主要有两种:异常声明throws 以及 try-catch捕获处理。
异常声明throws
处在方法声明时参数列表之后,当方法中抛出编译时异常,用户不想处理该异常,此时就可以借助throws将异常抛给方法的调用者来处理。即当前方法不处理异常,提醒方法的调用者处理异常。
修饰符 返回值类型 方法名(参数列表) throws 异常类型1,异常类型2...{ } //可以声明多个异常
注意:
- throws必须跟在方法的参数列表之后
- 声明的异常必须是 Exception 或者 Exception 的子类
- 方法内部如果抛出了多个异常,throws之后必须跟多个异常类型,之间用逗号隔开,如果抛出多个异常类型具有父子关系,直接声明父类即可。
- 调用声明抛出异常的方法时,调用者必须对该异常进行处理,或者继续使用throws抛出
try-catch捕获并处理
throws对异常并没有真正处理,而是将异常报告给抛出异常方法的调用者,由调用者处理。如果真正要对异常进行处理,就需要try-catch.
try{ // 将可能出现异常的代码放在这里 }catch(要捕获的异常类型 e){ // 如果try中的代码抛出异常了,此处catch捕获时异常类型与try中抛出的异常类型一致时,或者是try中抛出异常的基类时,就会被捕获到 // 对异常就可以正常处理,处理完成后,跳出try-catch结构,继续执行后序代码 }[catch(异常类型 e){ // 对异常进行处理 }finally{ // 此处代码一定会被执行到 }] // 后序代码 // 当异常被捕获到时,异常就被处理了,这里的后序代码一定会执行 // 如果捕获了,由于捕获时类型不对,那就没有捕获到,这里的代码就不会被执行
注:
- [ ]中表示可选项,可以添加,也可以不用添加
- try中的代码可能会抛出异常,也可能不会
注意:
- try块内抛出异常位置之后的代码将不会被执行
- 如果抛出异常类型与catch时异常类型不匹配,即异常不会被成功捕获,也就不会被处理,继续往外抛,直到JVM收到后 中断程序----异常是按照类型来捕获的
- try中可能会抛出多个不同的异常对象,则必须用多个catch来捕获----即多种异常,多次捕获
- 可以通过一个catch捕获所有的异常,即多个异常,一次捕获(用它们的父类异常捕获,如果这样做我们就不知道发生了什么异常,因此不推荐)
在catch 语句里我们有三种处理异常方法:
只打印异常信息:System.out.println(e.getMessage());
打印异常类型,异常信息:System.out.println(e);
打印信息最全面:e.printStackTrace();
finally
try{ // 可能会发生异常的代码 }catch(异常类型 e){ // 对捕获到的异常进行处理 }finally{ // 此处的语句无论是否发生异常,都会被执行到 } // 如果没有抛出异常,或者异常被捕获处理了,这里的代码也会执行
finally中的代码一定会执行的,一般在finally中进行一些资源清理的扫尾工作。
自定义异常
Java 中虽然已经内置了丰富的异常类, 但是并不能完全表示实际开发中所遇到的一些异常,此时就需要维护符合我们实际情况的异常结构。
实现方式:
- 自定义异常类,然后继承自Exception 或者 RunTimeException
- 实现一个带有String类型参数的构造方法,参数含义:出现异常的原因
注意:
自定义异常通常会继承自 Exception 或者 RuntimeException
继承自 Exception 的异常默认是受查异常
继承自 RuntimeException 的异常默认是非受查异常
举个例子,实现一个用户登录功能:
public class Test { public static void fun(String name2, String code2) throws PasswordException, UsernameException { String name = "张三"; String code = "666666"; if(!name2.equals(name)) { //如果输入的姓名或密码错误,则抛出异常 throw new PasswordException("用户名错误!"); } if(!code2.equals(code)) { throw new UsernameException("密码错误!"); } System.out.println("输入成功!!!"); } public static void main(String[] args) throws PasswordException, UsernameException { Scanner scanner = new Scanner(System.in); String name = scanner.nextLine(); String code = scanner.nextLine(); fun(name,code); } }
public class PasswordException extends Exception{ public PasswordException(String message) { super(message); } }
public class UsernameException extends Exception{ public UsernameException(String message) { super(message); } }