目前多态情况下不能访问子类特有的成员
如果需要访问子类特有的成员,那么需要进行类型强制转换,转换为子类的类型然后再进行调用
基本数据类型的转换:
小数据类型-------------》大数据类型 不会报错 自动类型转换
大数据类型-------------》小数据类型 可能会损失精度 (强制类型转换) 小数据类型 变量名=(小数据类型) 大数据类型
引用数据类型之间的转换:
小数据类型-------------》大数据类型 不会报错 自动类型转换
大数据类型-------------》小数据类型 强制类型转换
类型转换最常见的问题:java.lang.ClassCastException 强制类型转换失败
实现关系下的多态:
接口 变量=new 接口实现类的对象
public class Demo2 { public static void main(String[] args) { // TODO Auto-generated method stub //实现关系下的多态 Dao d=new UserDao(); d.add(); } } interface Dao{ public void add(); public void delete(); } class UserDao implements Dao{ public void add() { System.out.println("添加元素成功!"); } public void delete() { System.out.println("删除元素成功!"); } }
结果为:添加元素成功!
对于这种多态,只会调用实现类的方法,因为接口中的方法都是抽象的,所以都是非静态的,所以永远都是调用子类的方法,即为实现类的方法
内部类:一个类定义在另外一个类的内部,那么该类就称作为内部类
内部类的class文件名:外部类$内部类.class
好处:便于区分该class文件是属于哪个外部类的
内部类的类别:
- 成员内部类
- 局部内部类
成员内部类的访问方式:
1.在外部类提供一个方法创建内部类的对象进行访问
public class InnerClass { public static void main(String[] args) { // TODO Auto-generated method stub Outer outer=new Outer(); outer.instance(); } } class Outer { int x=100; //成员变量 class Inner { //内部类与成员变量可以看作是一个等级的 int i=10; public void print() { System.out.println("这个是成员内部类的print方法!"); } } //在外部类的方法中创建了内部类的对象,然后调用内部方法 public void instance() { Inner inner=new Inner(); inner.print(); } }
2.在其他类直接创建内部类的对象 格式:外部类.内部类 变量名=new 外部类().new 内部类();
注意:如果是一个静态内部类,那么在其他类创建的格式: 外部类.内部类 变量名=new.外部类().内部类;
public static void main(String[] args) { // TODO Auto-generated method stub //Outer outer=new Outer(); //outer.instance(); Outer.Inner inner=new Outer().new Inner(); inner.print(); }
内部类的好处:内部类可以直接访问外部类的所有成员
内部类的应用场景:我们在描述事物A的时候,发现描述的A事物内部还存在另外一个比较复杂的事物B的时候,而且这个比较复杂的事物B还需要访问A事物的属性等数据,那么这时候我们就可以使用内部类描述B事物
内部类要注意的细节:
- 如果外部类与内部类存在同名的成员变量时,在内部类中默认情况下是访问内部类的成员变量。可以通过外部类.this.成员变量名指定访问外部类的成员。
- 私有的成员内部类只能在外部类提供一个方法创建内部类的对象进行访问,不能在其他类创建对象了
- 成员内部类一旦出现了静态的成员,那么该类也必须使用static修饰
class Outer { int x=100; //成员变量 class Inner { //内部类与成员变量可以看作是一个等级的 int x=200; int i=10; public void print() { System.out.println("这个是成员内部类的print方法!"); System.out.println("x=:"+x); } } 结果:这个是成员内部类的print方法! x=:200
class Outer { int x=100; //成员变量 class Inner { //内部类与成员变量可以看作是一个等级的 int x=200; int i=10; public void print() { System.out.println("这个是成员内部类的print方法!"); System.out.println("x=:"+Outer.this.x); } } 结果:这个是成员内部类的print方法! x=:100
3. public class InnerClass { public static void main(String[] args) { // TODO Auto-generated method stub Outer outer=new Outer(); outer.instance(); //Outer.Inner inner=new Outer().new Inner(); //inner.print() System.out.println(Outer.Inner.a); } } class Outer { int x=100; //成员变量 static class Inner { //内部类与成员变量可以看作是一个等级的 int x=200; int i=10; static int a=10; public void print() { System.out.println("这个是成员内部类的print方法!"); System.out.println("a=:"+a); } }
public void instance() {
Inner inner=new Inner();
inner.print();
}
结果:
这个是成员内部类的print方法!
a=:10
10
如果是静态内部类,那么以前的创建对象的格式就错误了(Outer.Inner inner=new Outer().new Inner();)
现在应该是以下这样:
Outer.Inner inner=new Outer.Inner(); inner.print();
局部内部类:那么另外一个类就称作为局部内部类
定义:
public class LocalInnerClass { public static void main(String[] args) { // TODO Auto-generated method stub Outer1 outer=new Outer1(); outer.test(); } } class Outer1{ public void test() { int y=10; //局部变量 class Inner1{ int x=10; public void print() { System.out.println("这个是局部内部类的print方法!"); } } Inner1 inner=new Inner1(); inner.print(); } }
局部内部类要注意的细节:
- 如果局部内部类访问了一个局部变量,那么该局部变量必须使用final修饰 解决方案:如果一个局部内部类访问一个局部变量的时候,那么就让该局部内部类访问这个局部变量的复制品。
匿名内部类:没有类名的类就称作为匿名内部类
匿名内部类的好处:简化书写
匿名内部类的使用前提:必须存在继承或者实现关系才能使用
例:
局部内部类做法: public class Anonymous { public static void main(String[] args) { // TODO Auto-generated method stub Outer3 outer=new Outer3(); outer.print(); } } abstract class Animal2{ public abstract void run(); } class Outer3{ public void print() { //需求:在方法体内部定义一个类继承Animal类,然后调用run方法 class Dogs extends Animal2{ public void run() { System.out.println("狗在跑!"); } } //创建对象 Dogs d=new Dogs(); d.run(); } } 结果:狗在跑!
匿名内部类只是没有类名,其他的一概成员都是具备的。
public class Anonymous { public static void main(String[] args) { // TODO Auto-generated method stub Outer3 outer=new Outer3(); outer.print(); } } abstract class Animal2{ public abstract void run(); } class Outer3{ public void print() { //需求:在方法体内部定义一个类继承Animal类,然后调用run方法 //匿名内部类 new Animal2(){ //匿名内部类的成员 public void run() { System.out.println("狗在跑!"); } }.run(); } } 结果:狗在跑!
对于以上代码:
- 匿名内部类与Animal2是继承的关系
- 目前这个new不是创建Animal的对象,而是创建Animal子类的对象,只是借用了父类的名字
- 匿名内部类也可以有自己特有的方法
new Animal2(){ //匿名内部类的成员 public void run() { System.out.println("狗在跑!"); } public void bite() { System.out.println("狗在咬人!"); } }.bite(); 结果:狗在咬人!
当需要调用的函数为多个时,可以将某个函数的返回值类型void改为父类类型,然后再用this指代,具体如下:(其中注释的代码中有局部内部类对此问题的解决方法)
public class Anonymous { public static void main(String[] args) { // TODO Auto-generated method stub Outer3 outer=new Outer3(); outer.print(); } } abstract class Animal2{ public abstract Animal2 run(); public abstract void sleep(); } class Outer3{ public void print() { //需求:在方法体内部定义一个类继承Animal类,然后调用run方法和sleep方法 /*class Dogs extends Animal2{ public void run() { System.out.println("狗在跑!"); } public void sleep() { System.out.println("狗趴着睡觉!"); } } //创建对象 Dogs d=new Dogs(); d.run(); d.sleep(); */ //匿名内部类 new Animal2(){ //匿名内部类的成员 public Animal2 run() { System.out.println("狗在跑!"); return this; } public void sleep() { System.out.println("狗趴着睡觉!"); } public void bite() { System.out.println("狗在咬人!"); } }.run().sleep();; } } 结果为:狗在跑! 狗趴着睡觉!
当然,局部内部类中也可以使用如上所述方法,使得run返回this,由于返回值也不会去调用,但也可以输出结果
class Dogs extends Animal2{ public Animal2 run() { System.out.println("狗在跑!"); return this; } public void sleep() { System.out.println("狗趴着睡觉!"); } } //创建对象 Dogs d=new Dogs(); d.run(); d.sleep(); 结果:狗在跑! 狗趴着睡觉!
除了上述想法,我们也可以联想到,可以用一个变量来承接这个匿名类,使得我们也可以使用a.函数名()调用方法了1,那么这个变量是什么类型呢,我们可以想到Animal类型,那么此时我们就使用多态了。
Animal2 a=new Animal2(){ //多态的使用 Animal a=new 子类(); //匿名内部类的成员 public void run() { System.out.println("狗在跑!"); //return this; } public void sleep() { System.out.println("狗趴着睡觉!"); } public void bite() { System.out.println("狗在咬人!"); } }; a.run(); a.sleep(); 结果:狗在跑! 狗趴着睡觉!
//我现在把上面的代码修改回去了,run函数依旧是void类型,但其实不修改也可以,因为即使return this了之后,返回值也不会被使用,思路和上面局部内部类的的类似
实现关系下的匿名内部类:
public class Anonymous { public static void main(String[] args) { // TODO Auto-generated method stub Outer outer=new Outer(); outer.print(); } } interface Dao{ public void add(); } class Outer{ public void print() { //创建一个匿名内部类的对象 new Dao() { //接口并不能创建对象,这里是接口的实现类的对象,只是借用了接口的名字而已 public void add() { System.out.println("添加成功!"); } }.add();; } } 结果:添加成功!
匿名内部类一般是用于实参。
public class Anonymous { public static void main(String[] args) { // TODO Auto-generated method stub Outer outer=new Outer(); outer.print(); test(new Dao() { public void add() { System.out.println("添加员工成功!"); } }); } public static void test(Dao d) {//这里需要传接口的实现类,因为接口本身不能创建对象 d.add(); } } interface Dao{ public void add(); } 结果:添加成功! 添加员工成功!
以后基本接触到的都是作为实参使用。
异常:我们的Java程序也是会存在某些不正常的情况的,这些不正常的情况我们就统称为异常
在Java程序中也会出现程序不正常的情况,Java是面向对象的语言,任何的事物都可以使用类进行描述,那么这时候sun就使用了很多的类描述了Java程序中各种不正常的情况,而用于描述程序不正常的情况的类我们称作为异常类,很多异常堆积起来,就形成了java中的异常体系
异常体系
————————| Throwable (所有异常或者错误类的超类)
————————————| Error (错误)错误一般是由于jvm或者是硬件引发的问题,所以我们一般不会通过代码去处理错误的。
————————————| Exception (异常) 是需要通过代码区处理的
Throwable类:常用的方法
- toString() 返回此throwable的简短描述 返回当前异常对象的完整类名+病态信息 包名+类名=完整类名
- getMessage() 返回的是创建Throwable传入的字符串信息
- printStackTrace() 打印异常的栈信息 (因为异常就像是栈底的异常引起上面的异常,一个个累积,相当于进栈)
public static void main(String[] args) { // TODO Auto-generated method stub //创建了一个Throwable对象 Throwable t=new Throwable(); String info=t.toString(); System.out.println("toString: "+info); //java.lang.Throwable 包名+类名=完整类名 } 结果:toString: java.lang.Throwable
public static void main(String[] args) { // TODO Auto-generated method stub //创建了一个Throwable对象 Throwable t=new Throwable(); String info=t.toString(); String message=t.getMessage(); System.out.println("toString: "+info); //java.lang.Throwable 包名+类名=完整类名 System.out.println("message:"+message); } 结果:toString: java.lang.Throwable message:null //创建对象时括号中的就是病态信息
public static void main(String[] args) { // TODO Auto-generated method stub //创建了一个Throwable对象 Throwable t=new Throwable("头晕,感冒"); String info=t.toString(); String message=t.getMessage(); System.out.println("toString: "+info); //java.lang.Throwable 包名+类名=完整类名 System.out.println("message:"+message); } 结果:toString: java.lang.Throwable: 头晕,感冒 message:头晕,感冒
public class Exception { public static void main(String[] args) { // TODO Auto-generated method stub //创建了一个Throwable对象 /*Throwable t=new Throwable("头晕,感冒"); String info=t.toString(); String message=t.getMessage(); System.out.println("toString: "+info); //java.lang.Throwable 包名+类名=完整类名 System.out.println("message:"+message); */ test(); } public static void test() { Throwable throwable=new Throwable(); throwable.printStackTrace(); } } 结果: java.lang.Throwable at test3.Exception.test(Exception.java:17) at test3.Exception.main(Exception.java:14)
jvm在默认情况下只能管理64M内存
Error : 错误一般是由于jvm或者是硬件引发的问题,所以我们一般不会通过代码去处理错误的。
public static void main(String[] args) { // TODO Auto-generated method stub //jvm在默认情况下只能管理64M内存 byte[] buf=new byte[1024*1024*1024]; }
这个会产生内存超出的错误
Exception:是需要通过代码去处理的
如何区分代码与异常呢:
如果程序出现了不正常的信息,如果不正常的信息的类名是以Error结尾的,那么肯定是一个错误。
如果是以Exception结尾的,那么肯定就是一个异常
public class Error { public static void main(String[] args) { // TODO Auto-generated method stub //jvm在默认情况下只能管理64M内存 //byte[] buf=new byte[1024*1024*1024]; div(4,0); } public static void div(int a,int b) { int c=a/b; System.out.println("c="+c); } } 结果: Exception in thread "main" java.lang.ArithmeticException: / by zero at test3.Error.div(Error.java:12) at test3.Error.main(Error.java:9)
上面的结果是通过printStackTrace方法打印出来,那么异常对象从何而来呢?
答: jvm运行到a/b这个语句时,发现b为0,除数为0在我们现实生活中是属于不正常的情况,jvm一旦发现了这种不正常的情况时候,那么jvm就会马上创建一个对应的异常对象,并且会调用这个异常对象的printStackTrace的方法来处理
一般的异常运行到的时候就不会执行后面的语句了,所以为了让后面的执行,需要处理异常
异常的处理:
方式一:捕获处理
捕获处理的格式:
try{
可能发生异常的代码
}catch(捕获的异常类型 变量名){
处理异常的代码。。。
}
public class Error { public static void main(String[] args) { // TODO Auto-generated method stub //jvm在默认情况下只能管理64M内存 //byte[] buf=new byte[1024*1024*1024]; div(4,0); } public static void div(int a,int b) { int c=0; try { c=a/b; //jvm在这句话的时候发现了不正常的情况,那么就会创建一个对应的异常对象 }catch(ArithmeticException e) { //捕获异常类型 //处理异常的方法 System.out.println("toString:"+e.toString()); } System.out.println("c="+c); } } 结果: toString:java.lang.ArithmeticException: / by zero c=0
这时候就会输出c了
捕获处理要注意的细节:
- 如果try块中的代码出现了异常经过了处理之后,那么try-catch块外面的代码可以正常的执行
- 如果try块中出现了异常的代码,那么在try块中出现异常代码后面的代码是不会执行了
- 一个try块后面是可以跟有多个catch块的,也就是一个try块可以捕获多种异常的类型
- 一个try块可以捕获多种异常的类型,但是捕获的异常类型必须从小到大进行捕获,否则编译报错 (比如捕获Exception 类型就得放在最下面的catch,因为其是所有异常类型的父类,否则就让其他代码都是废话了,编译就会报错)
public class Error { public static void main(String[] args) { // TODO Auto-generated method stub //jvm在默认情况下只能管理64M内存 //byte[] buf=new byte[1024*1024*1024]; int[] arr=null; div(4,2,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("toString:"+e.toString()); }catch(NullPointerException e) { System.out.println("出现了空指针异常"); } System.out.println("c="+c); } } 结果: 出现了空指针异常 c=2
方式二:抛出处理
throw
throws
抛出处理要注意的细节:
- 如果一个方法的内部抛出了一个编译时异常对象,那么必须要在方法上声明抛出
- 如果调用了一个声明抛出编译时异常的方法,那么调用者必须要处理异常
- 如果一个方法内部抛出了一个异常对象,那么throw语句后面的代码都不会再执行了(一个方法遇到了throw关键字,该方法也会马上停止执行的)
- 在一种情况下,只能抛出一种类型的异常
public class Test { public static void main(String[] args) { // TODO Auto-generated method stub try { div(4,0); //此方法调用了一个抛出异常的方法,所以它就必须要处理这个异常,所以用try-catch方法 }catch(Exception e) { System.out.println("出现异常了..."); e.printStackTrace(); } } public static void div(int a,int b) throws Exception{ //声明抛出 if (b==0) { throw new Exception(); } int c=a/b; System.out.println("c="+c); } } 结果: 出现异常了... java.lang.Exception at test.Test.div(Test.java:16) at test.Test.main(Test.java:8)
当然也可以在div函数再次抛出
public class Test { public static void main(String[] args) throws Exception{ //如果这样抛出异常,那么这时候调用者就是main也就是jvm,所以jvm会打印异常信息 // TODO Auto-generated method stub //try { div(4,0); //此方法调用了一个抛出异常的方法,所以它就必须要处理这个异常,所以用try-catch方法 /*}catch(Exception e) { System.out.println("出现异常了..."); e.printStackTrace(); }*/ } public static void div(int a,int b) throws Exception{ if (b==0) { throw new Exception(); } int c=a/b; System.out.println("c="+c); } } 结果: Exception in thread "main" java.lang.Exception at test.Test.div(Test.java:16) at test.Test.main(Test.java:8)
以下为需要注意的第四点的代码 public static void main(String[] args){ //如果这样抛出异常,那么这时候调用者就是main也就是jvm,所以jvm会打印异常信息 // TODO Auto-generated method stub try { int[] arr=null; div(4,2,arr); //此方法调用了一个抛出异常的方法,所以它就必须要处理这个异常,所以用try-catch方法 }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); } 结果: 出现异常了... java.lang.NullPointerException at test.Test.div(Test.java:20) at test.Test.main(Test.java:9)
throw与throws两个关键字:
- throw关键字是用于方法内部的,throws是用于方法声明上的
- throw关键字是用于方法内部抛出一个异常对象的,throws关键字是用于在方法声明上声明抛出异常类型的
- throw关键字后面只能有一个异常对象,throws后面一次可以声明抛出多种类型的异常
何时使用抛出处理
如果你需要通知到调用者,你的代码出了问题,那么这时候就使用抛出处理
如果代码是直接与用户打交道,遇到了异常千万不要再抛,再抛的话,就给了用户了,这时候就应该使用捕获处理。