面向对象 (下)
面向对象
1、继承
1.1 继承的概念
继承描述的是事物之间的所属关系,通过继承可以使多种事物之间形成一种关联体系。
让类与类之间产生了子父类的关系
1.2 继承的关键字
子类 extends 父类
1.3 java中的继承的特点
只支持单继承。但是可以多层继承
1.4 继承父类只能使用父类公共的成员
1.5 继承的好处和弊端
好处:提高了代码的复用性,提高代码的维护性
弊端:类和类之间的耦合性太强
2、重写父类方法
2.1 定义
在继承关系中,子类会自动继承父类中公共的方法,但有时在子类中需要对继承的方法进行一些修改,即对父类的方法进行重写。
2.2 注意
(1)子类中重写的方法需要和父类被重写的方法具有相同的方法名、参数列表以及返回值类型。
(2)子类重写父类方法时,不能使用比父类中被重写的方法更严格的访问权限
2.3 为什么要进行方法的重写?
方法的重写发生在子父类关系中
当父类提供的功能不满足子类具体的需求的时候,那么就需要对父类的方法进行重写
2.4 @Override注解
强制检查该方法是否是重写的方法
2.5 相关代码
Cat类
public class Cat extends Animal{
@Override //强制检查该方法是否是重写的方法
public void eat(){
System.out.println("猫吃鱼...")
}
}
Dog类
public class Dog extends Animal{
@Override //强制检查该方法是否是重写的方法
public void eat(){
System.out.println("狗吃肉...")
}
}
主函数类
public class Demo1{
public static void main(String[] args){
Cat c = new Cat();
c.eat();
Dog d = new Dog();
d.eat();
}
}
3、super关键字
在继承关系中,当子类重写父类的方法后,子类对象将无法直接访问父类被重写的方法。
所以利用super关键字来访问父类的成员。例如访问父类的成员变量、成员方法和构造方法,
Cat类
public class Cat extends Animal{
@Override //强制检查该方法是否是重写的方法
public void eat(){
super.eat();
System.out.println("猫吃鱼...")
}
}
如果想要构造有参的构造方法,可以对比父类的成员变量,来通过super关键字,构造有参的构造方法
public class Cat extends Animal{
public Cat(){
super();
}
public Cat(String name, int age, String color){
super(name,age,color);
}
}
4、Object类
在java中提供了一个Object类,它是所有类的父类,即每个类都直接或间接继承自该类
Object类通常被称之为超类、基类或根类。
当定义一个类时,如果没有使用extends关键字为这个类显示地指定父类,那么该类会默认继承Object类
4.1 tostring方法没有重写之前
public String toString(){
return getClass().getName()+“@”+Integer.toHexString(hashCode());
getClass(); 获取当前运行对象的字节码对象
getName(); 获取包名和类名
@:固定的连接符
hashCode(); 获取模拟出来的内存地址值
Integer.toHexString(int num); 将十进制的整数转换成十六进制
}
5、final关键字
final关键字可用于修饰类、变量和方法。
它有“不可更改” 或者 “最终” 的含义。因此被final修饰的类、变量和方法将具有以下特性。
final修饰的类不能被继承
final修饰的方法不能被子类重写
final修饰的变量(成员变量和局部变量)是常量,只能赋值一次。
6、抽象类
6.1 什么是抽象类?
当我们进行父类抽取的时候,有些方法具体每个子类的实现方式都不太一样。
那么这个时候,就应该把这个方法定义成抽象方法。有抽象方法的类一定是抽象类。
6.2 抽象类和抽象方法如何定义
abstract
6.3 抽象类的成员特点
成员变量:既可以有变量、也可以有常量
成员方法:既可以有抽象方法、也可以有非抽象方法
构造方法:可以有构造方法
6.4 抽象类的注意事项
抽象类不能直接创建对象使用
6.5 相关代码
大致结构
public abstract class Animal{
.....
public abstract void eat(); // 抽象方法没有方法体,子类继承后可以重写
}
7、接口
7.1 接口的概念
如果一个抽象类中的所有方法都是抽象的,则可以将这个类定义为Java中的另一种形式----接口。接口是一种特殊的抽象类,它不能包含普通方法,其内部的所有方法都是抽象方法,它将抽象进行得更为彻底。
在JDK8中,对接口进行了重新定义,接口中除了抽象方法外,还可以有默认方法和静态方法(也叫类方法),默认方法使用default修饰,静态方法使用static修改,并且这两种方法都允许有方法体。
与定义类不同的是,在定义接口时,不再使用class关键字,而是使用interface关键字来声明。接口定义的基本语法格式如下:
7.2 格式
[修饰符] interface 接口名 [extends 父接口1,父接口2....]{
[public] [static] [final] 常量类型 常量名 = 常量值;
[public] [abstract] 方法返回值类型 方法名([参数列表]);
[public] default 方法返回值类型 方法名([参数列表]){
//默认方法的方法体
}
[public] static 方法返回值类型 方法名([参数列表]){
//类方法的方法体
}
}
7.3 什么是接口?
接口是一种更加抽象的类,它完成的是一种特性的功能。
接口的出现,打破了单继承的局限性
7.4 接口如何定义
public interface 接口名{
//常量
//抽象方法
//默认方法
//静态方法
}
7.5 接口中不能有构造方法
接口中不能有构造方法
接口不能直接创建对象使用
7.6 接口和类之间的关系
实现关系: implements
而且可以多实现
7.7 接口和接口之间的关系
继承关系:extends
可以多继承
7.8 代码
//创建一个接口
public interface Animal{
//提供一个常量
public static final int ID=1;
//抽象方法
public abstract void breath();
//默认的方法
public default void getType(String type){
System.out.println("当前动物是属于:"+ type);
}
//静态方法
public static int getId(){
return ID;
}
}
//接口里的抽象方法,类来实现接口时,必须将抽象方法都实现
public class Dog implements Animal{
@Override
public void breath(){
System.out.println("狗在呼吸");
}
}
8、多态
8.1 定义
在Java中,多态是指不同类的对象在调用同一个方法时所呈现出的多种不同行为。
8.2 说明
通常来说,在一个类中定义的属性和方法被其他类继承或重写后,当把子类对象直接赋值给父类引用变量时,相同引用类型的变量调用同一个方法所呈现出的多种不同形态。
8.3 作用
通过多态,消除了类之间的耦合关系,大大提高了程序的可扩展性和可维护性。
8.4 什么是多态?
指的是事物的多种变化形态
例如:猫是一只动物,猫是一只猫
8.5 多态的前提条件
要有继承或者实现的关系
要有方法重写
要有父类引用指向子类对象
8.6 多态的成员访问特点
成员变量:编译看父类,运行看父类
成员方法:编译看父类,运行看子类
8.7 多态的好处和弊端
好处:提高代码的扩展性和维护性
弊端:无法使用子类特有的成员
8.8 多态的使用场景
可以作为方法的参数和返回值进行使用,可以提高代码的扩展性
9、对象的类型转换
9.1 向上转型
父类引用指向子类对象
9.2 向下转型
由父类引用转成一个对应的真实的子类对象
格式:目标对象类型 对象名 = (目标对象类型)被转换的引用
9.3 代码
//代码1
public class Demo{
public static void main(String[] args){
Animal a = new Dog(); //向上转型 将一个子类对象赋值给了一个父类型的对象引用
//向下转型
Dog a = (Dog)a;
a.lookhome(); //调用子类独有的方法
}
}
//代码2
public class Demo{
public static void main(String[] args){
Dog d = new Dog();
method(d);
Cat c = new Cat();
method(c);
}
public static void method(Animal a){
a.eat();
//向下转型
if(a instanceof Dog)
{
Dog d = (Dog)a;
d.lookHome(); //调用子类的方法
}
else if(a instanceof Cat)
{
Cat c = (Cat)a;
c.cathMouse(); //调用子类的方法
}
}
}
10、内部类
10.1 概念
在java中,允许在一个类的内部定义类,这样的类称作内部类,这个内部类所在的类称作外部类
10.2 分类
成员内部类、局部内部类、静态内部类、匿名内部类
10.3 成员内部类
10.3.1 定义
在一个类中除了可以定义成员变量、成员方法、还可以定义类,这样的类被称作成员内部类
10.3.2 说明
在成员内部类中,可以访问外部类的所有成员,包括成员变量和成员方法;
在外部类中,同样可以访问成员内部类的变量和方法。
创建内部类对象的格式:
外部类名.内部类名 对象名 = new 外部类对象().new 内部类对象();
public class Outer{
//外部类的成员变量
int outer = 10;
//外部类的成员方法
public void outerMethod(){
System.out.println("外部类成员方法");
}
//成员内部类
class Inner{
//内部类的成员变量
int inner = 20;
//内部类的成员方法
public void innerMethod(){
System.out.println("内部类的成员方法");
}
}
}
//主函数
public class Demo1{
public static void main(String[] args){
//创建内部类对象
Outer.Inner oi = new Outer().new Inner();
System.out.println(oi.inner);
oi.innerMethod();
}
}
10.4 局部内部类
10.4.1 定义
局部内部类,也叫做方法内部类,就是定义在某个局部范围中的类,它和局部变量一样,都是在方法中定义的,其有效范围
只限于方法内部。
10.4.2 说明
在局部内部类中,局部内部类可以访问外部类的所有成员变量和方法,而局部内部类中的变量和方法却只能在创建该局部内部类的方法中进行访问。
public class Outer{
//外部类的成员变量
int outer = 10;
//外部类的成员方法
public void outerMethod(){
System.out.println("外部类成员方法");
}
public void function(){
//局部内部类
class Inner{
//内部类的成员变量
int inner = 20;
//内部类的成员方法
public void innerMethod(){
System.out.println("内部类的成员方法");
System.out.println(outer);
outerMethod();
}
}
Inner i = new Inner();
System.out.println(i.inner);
i.innerMethod();
}
}
//访问局部内部类,只需要创建外部类对象。调用所属方法即可
public class Demo1{
public static void main(String[] args){
Outer o = new Outer();
o.function();
}
}
10.5 静态内部类
10.5.1 定义
使用static关键字修饰的成员内部类
10.5.2 说明
静态内部类在成员内部类前增加了static关键字,在功能上,静态内部类中只能访问外部类的静态成员,同时通过外部类访问静态内部类成员时,可以跳过外部类从而直接通过内部类访问静态内部类成员。
创建静态内部类对象的具体语法格式如下:
外部类名.静态内部类名 变量名 = new 外部类名.静态内部类名();
public class Outer{
//外部类的成员变量
static int outer = 10;
//静态成员内部类
static class Inner{
//内部类的成员变量
static int inner = 20;
//内部类的成员方法
public static void innerMethod(){
System.out.println("内部类的成员变量:" + inner);
System.out.println("外部类的成员变量:" + outer);
}
}
}
//创建对象格式
//外部类名.内部类名 对象名 = new Outer.Inner();
public class Demo1{
public static void main(String[] args){
Outer.Inner oi = new Outer.Inner();
oi.innerMethod();
//或者
Outer.Inner.innerMethod();
}
}
10.6 匿名内部类
10.6.1 定义
匿名内部类其实就是没有名称的内部类。
10.6.2 说明
在调用包含有接口类型参数的方法时,通常为了简化代码,可以直接通过匿名内部类的形式传入一个接口类型参数,在匿名内部类中直接完成方法的实现。
10.6.3 匿名内部类的前提
必须是类或者接口
10.6.4 格式
new 类名/接口名(){
重写抽象方法
}
public abstract class Animal{
public abstract void eat();
}
public class Demo1{
public static void main(String[] args)
{
//整体就等效于:是Animal父类的子类对象
//第一种方法
new Animal(){
@Override
public void eat(){
System.out.println("狗吃肉");
}
}.eat();
//第二种方法
String name = "哈士奇"; //通过匿名内部类访问局部变量。在JDK8版本之前,必须加final关键字
Animal a = new Animal(){
@Override
public void eat(){
System.out.println("狗吃肉");
}
};
a.eat();
}
}
10.7 Lambda表达式入门
10.7.1 什么是Lambda表达式?
Lambda表达式是JDK8中的一个重要的新特性,它使用一个清晰简洁的表达式来表达一个接口,同时Lambda表达式也简化了对集合以及数组数据的遍历、过滤和提取等操作。
10.7.2 使用前提
必须是接口,接口中只能有一个抽象方法
10.7.3 使用格式
(数据类型 参数名1,数据类型 参数名2…)->{方法体}
(): 代表的是要执行接口中对应的那个抽象方法。如果方法中有参数,那么就需要传递参数。
->: 固定格式。作用是将小括号中的参数传递给后面的大括号方法体中。
{}:代表的是实现接口中那个抽象方法后的方法体。
public class Demo1{
public static void main(String[] args){
//匿名内部类的实现方式
useAnimal(new Animal(){
@Override
public void eat(){
System.out.println("狗吃肉");
}
});
//Lambda表达式的实现方法
useAnimal(()->{ System.out.println("狗吃肉");});
}
public static void useAnimal(Animal a){
a.eat();
}
}
10.8 函数式接口
10.8.1 定义
在JDK8中,接口上标注有@FunctionalInterface注解的即为函数式接口,在函数式接口内部有且只有一个抽象方法。
10.8.2 @FunctionalInterface
用这个注解来约束接口是一个函数式接口,也只是显示的标注了接口是一个函数式接口,并强制编辑器进行更严格的检查,确保该接口是函数式接口。
10.8.3
//调用无参数无返回值类型的方法
public class Demo1{
public static void main(String[] args){
//输出一句话
method(()->{System.out.println("你好Lambda表达式");});
}
public static void method(InterA i){
i.print();
}
}
/**使用Lambda表达式去调用一个有参数有返回值类型的方法
*
*
*Lambda表达式的省略规则:
*如果方法中只有一个参数,那么数据类型和小括号都可以省略
*如果方法中有多个参数,那么数据类型可以省略
*/
public class Demo2{
public static void main(String[] args){
//需求:通过Lambda表达式来调用method方法。来计算一下10+20的结果
method(10,20,(int num1,int num2)->{return num1 + num2;});
}
public static void method(int num1,int num2,InterB i){
int result = i.getSum(num1,num2);
System.out.println(result);
}
}
10.9 方法引用与构造器引用
10.9.1 说明
Lambda表达式的主体只有一条语句时,程序不仅可以省略包含主体的花括号,还可以通过英文的双冒号"::"的语法格式来引用方法和构造器(即构造方法)。
10.9.2 作用
可以进一步简化Lambda表达式的书写,其本质都是对Lambda表达式主体部分已存在的方法进行直接引用,主要区别就是对普通方法与构造方法的引用而已。
10.9.3 Lambda表达式支持的引用类型如下:
种类 Lambda表达式示例 对应的引用示例
类名引用普通方法 (x,y,…)->对象名x.类普通方法名(y,…) 类名::类普通方法名
类名引用静态方法 (x,y,…)->类名.类静态方法名(x,y…) 类名::类静态方法名
对象名引用方法 (x,y,…)->对象名.实例方法名(x,y…) 对象名::实例方法名
构造器引用 (x,y,…)->new 类名(x,y,…) 类名::new
示例1:获取一个数的绝对值
public class Math{
//获取一个数的绝对值
public static int abs(int num){
if(num<0){
return -num;
}else{
return num;
}
}
}
//静态方法引用的格式:
方法中(类名::静态方法名称)
public classDemo1{
public static void main(String[] args){
//使用Lambda表达式形式来调用method方法
method(-10,(int num)->{return Math.abs(num);});
//方法引用
method(-20,Math::abs);
/*
* 等效于:
*重写了calc方法
*public int calc(int num){
* return Math.abs(num);
*}
*/
}
public static void method(int num, Calcable c){
System.out.println(c.calc(num));
}
}
示例二:小写字母转换成大写字母
//对象名的方法引用
//格式
//方法(对象名::方法名)
public class Demo1{
public static void main(String[] args)
{
StringUtils su = new StringUtils();
//使用Lambda表达式来实现
method("abc",(String s)->{su.printUpperCase(s);});
//使用对象名引用方法来实现
method("abc",su::printUpperCase);
}
public static void method(String s,Printable p){
p.print(s);
}
}
示例三:输出名字
//构造方法引用格式
//方法(类名::new);
public class Demo1{
public static void main(String[] args)
{
//使用Lambda表达式来实现
method("张三",(String name)->{return new Person(name);});
//使用构造方法引用实现
method("王五",Person::new);
}
public static void method(String name,PersonBuild pb){
Person p = pb.buildPerson(name);
System.out.println(p);
}
}
示例四: 小写字母转换成大写字母
//类名引用普通方法
//方法(类名::普通方法名称)
public class Demo1{
public static void main(String[] args){
//使用Lambda表达式形式来使用
method(new StringUtils(),"abc",(StringUtils su,String s)->{su.printUpperCase(s);});
//使用类名引用普通方法的形式
method(new StringUtils(),"def",StringUtils::printUpperCase);
}
public static void method(StringUtils su,String s,String p){
p.print(su,s);
}
}
11、什么是异常?
11.1 Throwable类中的常用方法
方法声明 功能描述
String getMessage() 返回此throwable的详细消息字符串
void printStackTrace() 将此throwable及其追踪输出至标准错误流
void printStackTrace(PrintStream s) 将此throwable及其追踪输出到指定的输出流
String getMessage() 返回此throwable的详细消息字符串
11.2 异常的类型
11.2.1 异常类汇总
在Exception的子类中,除了RuntimeException类及其子类外,其他子类都是编译时异常。
11.2.2 特点
编译时异常的特点是在程序编写过程中,java编译器就会对编写的代码进行检查,如果出现比较明显的异常就必须对异常进行处理,否则程序无法通过编译。
11.2.3 处理编译时异常的方式如下
(1)使用try…catch 语句对异常进行捕获处理
(2)使用throws关键字声明抛出异常,让调用者处理
11.2.4 运行时异常
RuntimeException类及其子类都是运行时异常
11.2.4.1 特点
运行时异常是在程序运行时由java虚拟机自动进行捕获处理的,即使没有使用try…catch语句捕获或使用throws关键字声明抛出,程序也能编译通过,只是在运行过程中可能报错。
11.2.4.2 运行时常见的异常
异常类名称 异常类说明
ArithmeticException 算术异常
IndexOutOfBoundsException 角标越界异常
ClassCastException 类型转换异常
NullPointerException 空指针异常
NumberFormatException 数字格式化异常
11.2.4.3 运行时异常错误分析
运行时异常一般是由于程序中的逻辑错误引起的,在程序运行时无法恢复。
11.2.4.4 运行时异常示例
int[] arr = new int[5];
System.out.println(arr[5]);
12、处理异常
当程序发生异常时,会立即终止,无法继续向下执行。为了保证程序能够有效的执行,java中提供了一种对异常进行处理的方式—异常捕获
12.1 异常捕获的基本语法格式
try{
//可能发生异常的语句
}catch(Exception类或其子类 e){
//对捕获的异常进行相应的处理
}
12.1.1 示例
public class Demo1{
public static void main(String[] args){
try{
int num1 = 5;
int num2 = 0;
int result = num1/num2;
System.out.println(result);
}catch(ArithmeticException a){
a.printStackTrace();
}finally{
//finally一般适用于释放资源,无论是否出现异常,finally都会执行到
System.out.println("释放掉了系统资源");
}
System.out.println("这是我的后续代码");
}
}
12.2 throws关键字
12.2.1 throws关键字抛出异常的基本格式
用来在方法的声明上去声明抛出异常。多个异常类名中间使用逗号分割
[修饰符] 返回值类型 方法名([参数类型 参数名1…]) throws 异常类1,异常类2…{
//方法体
}
12.2.2 代码示例
public class Demo1{
public static void main(String[] args) throws Exception{
int num1 = 5;
int num2 = 0;
int result = num1/num2;
System.out.println(result);
System.out.println("这是我的后续代码");
}
}
12.3 throw关键字
程序开发中,除了可以通过throws关键字抛出异常外,还可以使用throw关键字抛出异常
12.3.1 二者对比
throws关键字用在方法声明中,用来指明方法可能抛出的多个异常。(用在方法外)
throw关键字用于方法体内,并且抛出的是一个异常类对象。(用在方法内)
12.3.2 代码示例
public class Demo1{
public static void main(String[] args) throws Exception{
printAge(-23);
}
public static void printAge(int age) throws Exception{
if(age<0 || age>200)
{
throw new Exception("年龄不在正确的范围");
}else{
System.out.println("这个人的年龄是:" + age);
}
}
}
public class Demo1{
public static void main(String[] args){
try{
printAge(-23);
}catch(Exception e){
System.out.println(e.getMessage());
}
}
public static void printAge(int age) throws Exception{
if(age<0 || age>200)
{
throw new Exception("年龄不在正确的范围");
}else{
System.out.println("这个人的年龄是:" + age);
}
}
}
13、自定义异常
java中定义了大量的异常类,虽然这些异常类可以描述编程时出现的大部分异常情况,但是在程序开发中有时可能需要描述程序中特有的异常情况,例如在设计divide()方法时,不允许被除数为负数。
13.1 解决方法
java允许用户自定义异常,但自定义的异常类必须继承自Exception或其子类
//自己定义的异常
public class MyDivideException extends Exception{
public MyDivideException(){
}
public MyDivideException(String s)
{
super(s);
}
}
//使用自己定义的异常
public class Demo1{
public static void main(String[] args){
try{
divide(6,0);
}catch(MyDivideException e){
e.printStackTrace();
}
}
public static void divide(int num1,int num2) throws MyDivideException{
if(num2==0)
{
throw new MyDivideException("除数不能为0");
}else{
System.out.println(num1/num2);
}
}
}
14、垃圾回收
14.1 回收的方法
(1)调用System类的gc()静态方法
System.gc()
(2)调用Runtime对象的gc()实例方法
Runtime.getRuntime.gc()
14.2 相关知识
当一个对象在内存中被释放时,它的finalize()方法会被自动调用,finalize()方法是定义在Object类中的实例方法。
任何java类都可以重写Object类的finalize()方法,在该方法中清理该对象占用的资源。如果程序终止之前仍然没有进行垃圾回收,则不会调用失去引用对象的finalize()方法来清理资源。
只有当程序认为需要更多的额外内存时,垃圾回收器才会自动进行垃圾回收。
14.3 代码示例
public class Demo2{
public static void main(String[] args){
}
public static void method1(){
//不手动介入的情况
Person p = new Person();
p=null;
for(int i=1;i<=10;i++)
{
System.out.println("aaa");
}
}
public static void method2(){
//手动介入的情况
Person p1 = new Person();
p1=null;
System.gc();
for(int i=1;i<=10;i++)
{
System.out.println("bbb");
}
}
}