目录
多态:
1. 多态用于形参类型的时候,可以接收更多类型的数据 。
2. 多态用于返回值类型的时候,可以返回更多类型的数据。
多态的好处: 提高了代码的拓展性。
需求1: 定义一个函数可以接收任意类型的图形对象,并且打印图形面积与周长。
MyShape.java
//图形类
abstract class MyShape{
public abstract void getArea();
public abstract void getLength();
}
做一个抽象图形类,然后让子类去继承,这个子类就是圆类,内部可以有求面积方法,与求周长的方法
Circle.java
然后我们在定义一个矩形类,里面也有求面积与周长的方法
Rect.java
然后下面是主类的方去实现.
上面说了多态的一种情况,就是父类应用类型变量指向了子类的对象
下面来说一下,接口引用类型变量,指向了接口实现类的对象
先来看一个接口
Demo5.java
package pxx.test;
interface Demo5{
int add(int num1,int num2);
void say();
}
再来看它的实现类
Demo6.java
package pxx.test;
public class Demo6 implements Demo5{
@Override
public int add(int num1, int num2) {
return num1 + num2;
}
@Override
public void say() {
System.out.println("我是Demo5");
}
}
再来看主类
Demo7.java
package pxx.test;
public class Demo7 {
public static void main(String[] args) {
//接口引用类型变量指向接口实现类对象
//普通方法全是调用子类的方法
Demo5 a = new Demo6();
int res = a.add(1,2);
a.say();
System.out.println(res);
}
}
运行结果:
成员内部类:
直接在一个类的内部,把另外一个类当成成员变量、方法一样对待就可以了
比如
下面说一下成员内部类几个需要注意的地方:
1.在内部类中,外部类与内部类存在同名成员变量时,默认情况是访问内部类成员变量如果我们想指定外部类的成员变量:Outer.this.外部成员变量 this前面代表this是哪一个对象来调用的。
说一句比较重要的话:非静态内部类隐含地持有对外部类实例的引用。因此,可以通过这个引用来访问外部类的非静态成员。反之,静态内部类不隐含地持有对外部类实例的引用。因此,不能直接访问外部类的非静态成员(只能直接访问静态成员)。
2.既然内部类是成员,自然就可以用public,private等来修饰,如果设置成private,那么就不能再其他类直接创建对象。只能通过内部打开的接口来调用者使用
3.成员内部类一旦出现静态成员,该类也要用static来修饰
上面是不是说明了普通类需要一个外部类的实例存在才能创建,我们可以按照下面的方式来进行创建
package com.pxx.test;
public class Test2 {
private int outerField = 42;
public class InnerClass {
public void displayOuterField() {
System.out.println("Outer Field from Inner Class: " + Test2.this.outerField);
}
}
public Test2() {
// 提供一个构造方法,这样就可以在main方法中创建Test2的实例,从而可以创建InnerClass的实例
}
public static void main(String[] args) {
Test2 test2 = new Test2(); // 创建外部类实例
InnerClass innerClass = test2.new InnerClass(); // 通过外部类实例创建内部类实例
innerClass.displayOuterField(); // 输出:Outer Field from Inner Class: 42
}
}
我们要在另外一个类里面去访问外部类的内部类,该如何访问
Demo4.java
package pxx.test;
public class Demo4 {
public static void main(String[] args) {
System.out.println(Outer1.Inner1.i);
}
}
class Outer1 {
int x = 100;
int i = 1000;
//成员内部类
static class Inner1 {
static int i = 0;
public void print() {
System.out.println("i = " + i);//同名成员先访问内部
}
}
}
我们看上面的代码:
我们都知道静态成员是先于对象存在。ok,那我们想一下,如果内部类定义了一个static成员,那么这个成员的访问方式就可new Outer().Inner.成员。(也就是类名.成员)这个怎么去访问,这样是访问不了的
但是此时,如果外部类对象不存在,没有实例化,那么内部类成员也就不存在,内部类不存在,那怎么去调用内部类的静态成员。(注意class加载不一定创建了对象)
问:成员内部类,如果是静态的怎么访问。静态函数,不能调用非静态函数,非静态成员变量,不能带this,因为这些都是基于对象存在。那么如果这个类是一个静态的呢?
普通类不能是静态的
局部类部类
(在函数内部写一个类):
局部内部类与局部变量一样,只能在方法类中去使用
我们可以直接在方法内部实例化一个对象,直接使用,直接调用(也是唯一的创建使用放法)
上面y是一个局部变量,局部内部类,访问一个局部变量,该变量必须用final修饰,问什么?
分析一下y什么时候从内存消失:方法执行完毕
Inner的对象什么时候从内存中消失:不在引用,等待垃圾回收机制回收,它才会消失。上面说明了,Inner的生命周期比局部变量y长,而且当y消失了,Inner还在访问y,这样感觉y的周期被延长了,违背设计原则,造一个复制品,那么在这个过程中,就不允许修改,所以把一个局部变量定义成final,final的意思是不能修改。
匿名内部类:
没有类名的类,就是匿名内部类,准确说是没有引用变量指向的类。
简化书写
使用前提:必须存在继承或者实现关系才能使用
匿名内部类只是没有类名,其他什么完全具备,所以借助父类或者接口的名字:
上面借助了父类的类名来做一个匿名内部类
上面就是匿名内部类的调用方式
上面这样没有引用类型指向的匿名内部类,是可以定义自己独特的方法的。
上面就是调用完一个方法的同时,返回一个对象在调用下一个方法
上面就是可以引用变量名来调用方法
接口实现关系下的匿名内部类
匿名内部类:一般用于实参,我们一定要考虑实际内存开销
先来看一个接口:
Dao.java
package pxx.test1;
public interface Dao {
void add();
}
主类Demo1.java
package pxx.test1;
class Demo1 {
public static void main(String[] args) {
test(new Dao() {
public void add() {
System.out.println("添加员工成功");
}
});
}
//调用这个方法...
public static void test(Dao d) {
//形参类型是一个接口引用..带名字的匿名内部类对象
d.add();
}
}
运行结果:
异常处理:
说白了,什么错误,该不该去捕获这些错误,以及如何判断错误等级,最后如何处理错误。
JAVA有一个健全的异常体系,有很多异常类,描述了java开发过程中不正常的情况。
我们可以看到下面的子类。
Throwable是所有异常类的一个父类,我们重点学父类的方法,因为父类的方法子类也有,完了,我们在掌握子类特有的方法
下面有两个子类:错误Error和异常Exception
Error:错误的话情节比较严重
Exception:分为运行时异常(不一定处理)与编译时异常(一定要去处理),但我建议最好都处理
异常体系:
--------| Throwable
------------| Error
------------| Exception
Throwable常用的方法:
toString() 返回当前异常对象的完整类名+病态信息。比getMessage()处理信息更高级
getMessage() 返回的是创建Throwable传入的字符串信息。这个信息我们可以自己定义
printStackTrace() 打印异常的栈信息。
.
上面是一小部分,展示的是printStackTrace的用法。生病生在哪里的一系列追踪36行-》30行出问题
具体说说异常的体系:
----------| Throwable 所有异常或者错误类的超类
--------------|Error 错误 错误一般是用于jvm或者是硬件引发的问题,所以我们一般不会通过代码去处理错误的。
--------------|Exception 异常 是需要通过代码去处理的。
如何区分错误与异常呢:
如果程序出现了不正常的信息,如果不正常的信息的类名是以Error结尾的,那么肯定是一个错误。
如果是以Exception结尾的,那么肯定就是一个异常。
上面就是一个错误,这个错误的原因是,我们没有去找到这个文件,这个情况出现的比较硬,不属于代码可以解决的范围
Demo13.java
package pxx.test1;
class Demo13 {
public static void main(String[] args) {
//java虚拟机在默认的情况下只能管理64m内存。
//这里太大可能就会造成溢出
byte[] buf = new byte[1024 * 1024 * 1024];
System.out.println("Hello World!");
}
}
下面就会报出一个内存溢出错误
但是像上面的错误,我们避免不了,这个是由于虚拟机与硬件环境造成的,与我们没有关系
错误我们尽力去避免,但是出现异常,就必须解决异常,要么捕获,要么进行抛出,特别是编译异常。
上面函数调用部分会出现一个算术异常,违背现实常理
疑问: 下面的信息是通过printStackTrace方法打印出来,那么异常对象从何而来呢?
Exception in thread "main" java.lang.ArithmeticException: / by zero
at Demo10.div(Demo10.java:10)
at Demo10.main(Demo10.java:5)
jvm运行到a/b这个语句的时候,发现b为0,除数为0在我们现实生活中是属于不正常的情况,jvm一旦发现了这种不正常的情况时候,那么jvm就会马上创建一个对应的异常对象,并且会调用这个异常对象 的printStackTrace的方法来处理。
我们应该对于用户输入的数据进行监控,有异常抛出。
异常的处理:
方式一:捕获处理
捕获处理的格式:
try{
可能发生异常的代码;
}catch(捕获的异常类型 变量名(随便起,这个就是jvm虚拟机创建的一个对象引用变量)){
处理异常的代码....
}
方式二:抛出处理
如果我们不知道捕获的是什么异常,就用Exception来替代,异常处理之后,程序就会接着往下面继续执行。
Demo3.java
package pxx.test1;
class Demo3 {
public static void main(String[] args) {
int[] arr = null;
div(4, 0, arr);
}
public static void div(int a, int b, int[] arr) {
int c = 0;
try {
c = a / b;//jvm在这句话的时候发现了不正常的情况,那么就会创建一个对应的异常对象。
System.out.println("数组的长度:" + arr.length);
} catch (ArithmeticException e) {
//处理异常的代码....
System.out.println("异常处理了....");
System.out.println("toString:" + e.toString());
} catch (NullPointerException e) {
System.out.println("出现了空指针异常....");
} catch (Exception e) {
System.out.println("我是急诊室,包治百病!");
}
System.out.println("c=" + c);
}
}
上面运行结果:
捕获处理要注意的细节:
1. 如果try块中代码出了异常经过了处理之后,那么try-catch块外面的代码可以正常执行。
2. 如果try块中出了异常的代码,那么在try块中出现异常代码后面的代码是不会执行了。
那么异常代码块之外的代码是会正常执行的。
3. 一个try块后面是可以跟有多个catch块的,也就是一个try块可以捕获多种异常的类型。
4. 一个try块可以捕获多种异常的类型,但是捕获的异常类型必须从小到大进行捕获,否则编译报错。
下面说一下抛出处理方法:
把异常的信息比如服务器本身的问题发送给服务器,服务端解决问题,发布新版本,但是有一些不该发送到服务器的数据,那么我们就来做一下其他处理,比如用户在下载过程中没有wifi了
抛出处理(throw、throws)
抛出,抛给谁?服务器?用户?。。。
1.如果一个方法的内部抛出了一个异常对象,那么必须要在方法上声明抛出。
2.如果调用了一个声明抛出异常的方法,调用者必须处理异常,在调用的地方处理异常
在什么情况下抛出异常,上面是在b==0的情况下,当然上面我们选择了异常捕获,我们还可以选择在此抛出,不处理,谁调用main?抛给jvm
3.一个方法抛出一个异常之后,那么抛出语句后面的语句就会停止执行,还有就是一个方法内部遇到throw关键字也会马上停止执行。
4.在函数内部,我们一次只能抛出一种异常对象,当然在函数声明上 可以抛出多个异常类型
下面说一下throw与throws区别:
throw用于内部,throws一个用于方法上面
throw关键字是用于方法内部抛出一个异常对象的,throws关键字是用于在方法声明上声明抛出异常类型的。
throw关键字后面只能有一个异常对象,throws后面一次可以声明抛出多种类型的 异常。
Demo4.java
package pxx.test1;
class Demo4 {
public static void main(String[] args) {
try {
int[] arr = null;
div(4, 0, arr); //这里会是算术异常,但是值catch一个Exception,就会走这条路
System.out.println("前面异常处理完了,我运行了");
} catch (Exception e) {
System.out.println("出现异常了...");
e.printStackTrace();
}
}
public static void div(int a, int b, int[] arr) throws Exception, NullPointerException {
if (b == 0) {
throw new Exception(); //抛出一个异常对象...
} else if (arr == null) {
throw new NullPointerException();
}
int c = a / b;
System.out.println("c=" + c);
}
}
运行结果:
疑问:何时使用抛出处理?何时捕获处理?原则是如何?
如果你需要通知到调用者,你代码出了问题,那么这时候就使用抛出处理.,如果代码是直接与用户打交道遇到了异常千万不要再抛,再抛的话,就给了用户了,这时候就应该使用捕获处理。