Java基础语法之面向对象的内部类
上一期,我们对面向对象的接口进行了介绍,接口在jdk8前可以理解为一个特殊一些的100%抽象类,是为了让java也可以实现的多继承的效果而存在.本期我将会带来面向对象最后一章内部类、匿名类,这一章中的匿名类是个重点,他为以后我们大数据相关的语法会有帮助,也是理解lambda表达式的基础。然后我会介绍一下java中的异常。好了,开始进入正题:
内部类就像名字一样,在一个类的内部定义的一个类,假设将一个类A定义在另一个类B里面,那么,里面的类A就成为内部类,B则称之为外部类
语法格式
【修饰符】 class 外部类{
【其他修饰符】 static class 内部类{
}
}
示例代码
public class TestInner{
public static void main(String[] args){
Outer.Inner in= new Outer.Inner();
in.inMethod();
Outer.Inner.inTest();
Outer.Inner.inFun(3);
}
}
class Outer{
private static int a = 1;
private int b = 2;
protected static class Inner{
static int d = 4;//可以
void inMethod(){
System.out.println("out.a = " + a);
// System.out.println("out.b = " + b);//错误的
}
static void inTest(){
System.out.println("out.a = " + a);
}
static void inFun(int a){
System.out.println("out.a = " + Outer.a);
System.out.println("local.a = " + a);
}
}
}
静态内部类的特点
语法格式:
【修饰符】 class 外部类{
【修饰符】 class 内部类{
}
}
示例代码:
public class TestInner{
public static void main(String[] args){
Outer out = new Outer();
Outer.Inner in= out.new Inner();
in.inMethod();
Outer.Inner inner = out.getInner();
inner.inMethod();
}
}
class Father{
protected static int c = 3;
}
class Outer{
private static int a = 1;
private int b = 2;
protected class Inner extends Father{
// static int d = 4;//错误
int b = 5;
void inMethod(){
System.out.println("out.a = " + a);
System.out.println("out.b = " + Outer.this.b);
System.out.println("in.b = " + b);
System.out.println("father.c = " + c);
}
}
public static void outMethod(){
// Inner in = new Inner();//错误的
}
public Inner getInner(){
return new Inner();
}
}
非静态内部类的特点
局部内部类就是在成员方法的内部声明的类
语法格式
修饰符】 class 外部类{
【修饰符】 返回值类型 方法名(【形参列表】){
【final/abstract】 class 内部类{
}
}
}
示例代码
class Outer{
private static int a = 1;
private int b = 2;
public static void outMethod(){
final int c = 3;
class Inner{
public void inMethod(){
System.out.println("out.a = " + a);
// System.out.println("out.b = " + b);//错误的,因为outMethod是静态的
System.out.println("out.local.c = " + c);
}
}
Inner in = new Inner();
in.inMethod();
}
public void outTest(){
final int c = 3;
class Inner{
public void inMethod(){
System.out.println("out.a = " + a);
System.out.println("out.b = " + b);//可以,因为outTest是非静态的
System.out.println("method.c = " + c);
}
}
Inner in = new Inner();
in.inMethod();
}
}
局部内部类的特点
public class TestInner{
public static void main(String[] args) {
A obj = Outer.method();
//因为如果c不是final的,那么method方法执行完,method的栈空间就释放了,那么c也就消失了
obj.a();//这里打印c就没有中可取了,所以把c声明为常量,存储在方法区中
}
}
interface A{
void a();
}
class Outer{
public static A method(){
final int c = 3;
class Sub implements A{
@Override
public void a() {
System.out.println("method.c = " + c);
}
}
return new Sub();
}
}
因为外部方法中的局部变量在传递给内部类后就在栈内存中销毁了,为了能访问到局部变量的值,java就利用final关键字,将它存放到了方法区内,所以内部类可以访问但不能修改值
new 类名或接口名(){子类或实现类的成员}
// 编写接口
public interface Runnable{
public abstract void run();
}
//声明接口实现类
public class MyRunnable implements Runnable{
public void run(){
while(true){
System.out.println("大家注意安全");
try
Thread.sleep(1000);
}catch(Exception e){
}
}
}
}
// 在测试类中,创建接口的实例并调用
public class Test{
public static void main(String[] args){
//如果MyRunnable类只是在这里使用一次,并且只创建它的一个对象
//分开两个.java源文件,反而不好维护
Runnable target = new MyRunnable();
Thread t = new Thread("安全提示线程",target);
t.start();
}
}
当我们这个子类,或者实现类是一次性的,那么还需要费尽心机来命名,我们最终所需要的仅仅只是为了使用对象去调用而已,此时就可以使用匿名类来实现这个需求:
public class Test{
public static void main(String[] args){
//MyRunnable类只是在这里使用一次,并且只创建它的一个对象,那么这些写代码更紧凑,更好维护
Runnable target = new Runnable(){
public void run(){
while(true){
System.out.println("大家注意安全");
try
Thread.sleep(1000);
}catch(Exception e){
}
}
}
};
Thread t = new Thread("安全提示线程",target);
t.start();
}
}
是不是感觉清爽了很多,代码也简化了很多?下面开始正式结束匿名内部类的语法格式和使用:
语法格式:
// 有参数
new 父类(【实参列表】){
重写方法...
}
//()中是否需要【实参列表】,看你想要让这个匿名内部类调用父类的哪个构造器,如果调用父类的无参构造,那么()中就不用写参数,如果调用父类的有参构造,那么()中需要传入实参
// 无参数
new 父接口(){
重写方法...
}
//()中没有参数,因为此时匿名内部类的父类是Object类,它只有一个无参构造
使用方式一: 匿名内部类的对象直接调用
// 创建接口
interface A{
void a();
}
public class Test{
public static void main(String[] args){
new A(){
@Override
public void a() {
System.out.println("aaaa");
}
}.a();
}
}
class B{
public void b(){
System.out.println("bbbb");
}
}
public class Test{
public static void main(String[] args){
new B(){
public void b(){
System.out.println("ccccc");
}
}.b();
}
}
使用方式二: 通过父类或父接口的变量多态引用匿名内部类的对象
interface A{
void a();
}
public class Test{
public static void main(String[] args){
A obj = new A(){
@Override
public void a() {
System.out.println("aaaa");
}
};
obj.a();
}
}
class B{
public void b(){
System.out.println("bbbb");
}
}
public class Test{
public static void main(String[] args){
B obj = new B(){
public void b(){
System.out.println("ccccc");
}
};
obj.b();
}
}
使用方式三: 匿名内部类作为实参
interface A{
void method();
}
public class Test{
public static void test(A a){
a.method();
}
public static void main(String[] args){
test(new A(){
@Override
public void method() {
System.out.println("aaaa");
}
});
}
}
我写了一个案例,大家可以通过案例感受一下匿名内部类的丝滑~:
public class Demo1 {
public static void main(String[] args) {
// 正常的接口实现
Danceable cat = new Cat();
cat.dance();
// 使用匿名内部类
Danceable cat1 = new Danceable(){
@Override
public void dance() {
System.out.println("猫跳钢管舞");
}
};
cat1.dance();
// 直接使用匿名内部类
new Danceable(){
@Override
public void dance() {
System.out.println("猫跳广场舞");
}
}.dance();
// 使用匿名内部类的对象
test(new Danceable() {
@Override
public void dance() {
System.out.println("猫跳鬼步舞");
}
});
// 超级简化版
// 使用匿名内部类的对象
test(() -> System.out.println("猫跳鬼步舞"));
}
// 匿名内部类的对象作为实参
public static void test(Danceable danceable){
danceable.dance();
}
}
// 定义一个接口
interface Danceable{
void dance();
}
// 定义一个接口实现类
class Cat implements Danceable{
@Override
public void dance() {
System.out.println("猫跳靴子舞");
}
}
示例中的超级简化版就是lambda表达式,我们后面会详细讲到,它的原理就是匿名内部类
此时,我们终于开始对static进行开刀了,从面向对象开始就使用它,但没有具体提到过,今天,来详细挖一挖static关键字的用法,
static关键字,是一个修饰符,它的作用除了将类的内部成员变为静态属性/方法外还可以:
成员变量,我们称为类变量,或静态变量,表示某个类的所有对象共享的数据
成员方法,我们称为类方法,或静态方法,表示不需要实例对象就可以调用的方法,使用“类名."进行调用
代码块,我们称为静态代码块,或静态初始化块,用于为静态变量初始化,每一个类的静态代码块只会执行一次,在类第一次初始化时执行
成员内部类,我们称为静态成员内部类,简称静态内部类,不需要外部类实例对象就可以使用的内部类,在静态内部类中只能使用外部类的静态成员
静态导入
import static 包.类名.静态成员;
import static 包.类名.*;
修饰符在一起使用时的问题:
修饰符名称 | 外部类 | 成员变量 | 代码块 | 构造器 | 方法 | 局部变量 |
---|---|---|---|---|---|---|
public | √ | √ | × | √ | √ | × |
protected | × | √ | × | √ | √ | × |
private | × | √ | × | √ | √ | × |
static | × | √ | √ | × | √ | × |
final | √ | √ | × | × | √ | √ |
abstract | √ | × | × | × | √ | × |
native | × | × | × | × | √ | × |
不能和abstract一起使用的修饰符?
(1)abstract和final不能一起修饰方法和类
(2)abstract和static不能一起修饰方法
(3)abstract和native不能一起修饰方法
(4)abstract和private不能一起修饰方法
static和final一起使用:
(1)修饰方法:可以,因为都不能被重写
(2)修饰成员变量:可以,表示静态常量
(3)修饰局部变量:不可以,static不能修饰局部变量
(4)修饰代码块:不可以,final不能修改代码块
(5)修饰内部类:可以一起修饰成员内部类,不能一起修饰局部内部类
首先提一下我们java开篇提到的注释:
注释: 给人看的说明文字
注解: 给程序看的说明,是一种标记
注解的构成:
@Target(ElementType.METHOD)//注解可以使用的目标位置,比如类,方法,成员变量等上面
@Retention// 注解的有效周期
@Inherited
@OCUMENTED
public@interface MyAnnotation{}
常用的注解
@Override
用于检测被修饰的方法为有效的重写方法,如果不是,则报编译错误!
只能标记在方法上。
它会被编译器程序读取。
@Deprecated
用于表示被标记的数据已经过时,不建议使用。
可以用于修饰 属性、方法、构造、类、包、局部变量、参数。
它会被编译器程序读取。
@SuppressWarnings
抑制编译警告。
可以用于修饰类、属性、方法、构造、局部变量、参数
它会被编译器程序读取。
@Target 元注解,用在注解上的注解,例如@Override只能用在方法上
ElementType.MTHOD用在方法上
ElementType.FIELD用在属性上
ElementType.TYPE用在类上
@Retention 表述注解的有效范围,有效周期
RetentionPolicy.SOURCE 只在源码中有效
RetentionPolicy.ClASS 只在字节码文件中有效
RetentionPolicy.RUNTIME 在运行期间有效
@iNHERITED 表示加了此注解的类的子类同样有此注解
@Documented 表示生成的API文档中有次注解信息
自定义注解
public @interface 名称{}
示例说明:
初识注解的时候,我将它理解为python中的装饰器,因为它的作用类似装饰器,但java的注解更加强大一些,比如@override,它可以对代码在书写的时候就进行判断.活用注解,可以减少我们很多很多没有注意到的bug
public class Singleton {
//私有的静态变量
private static Singleton instance = null;
//私有构造器
private Singleton() {
}
//静态的公共的方法获取实例
public static Singleton getInstance() {
if (instance == null)
instance = new Singleton();//第一次调用时创建对象
return instance;
}
}
public class Student {
//3.创建一个静态属性,赋值一个实例
private final static Student student=new Student();//只在类加载过程中执行一次,创建一个对象
//1.私有构造器
private Student() {
}
//2.提供公共的静态的方法返回一个此类的实例
public static Student getInstance() {
return student;//每次调用返回同一个对象
}
}
因为懒汉式的单例实现方式是事先为这个唯一实例开辟了一个null的空间,如果是多线程,那么就可能会创造出很多的实例出来
java中把常见的不同异常用不同的类表示,当发生某种异常时,JVM会创建该异常类型的对象(其中包含了异常详细信息),并且抛出来,然后程序员可以catch到这个异常对象,并根据需要进行相应处理,如果无法catch到这个异常对象,说明没有针对这个异常预备处理措施,那么这个异常对象将会导致程序终止
异常的根类是java.lang.Throwable,Java提供的所有异常类均继承自此类,其下有两个子类:java.lang.Error与java.lang.Exception,平常所说的异常指java.lang.Exception
Throwable体系:
Throwable中的常用方法:
由于Error情况发生是我们无法处理的,一般因为是内存不足或程序存在严重逻辑问题,只能通过扩大内存或重新修改代码解决。
而我们平常所遇到的大多数是Exception类型的异常,也是我们通常说的异常和异常处理。Exception异常又通常分两大类:
运行期异常(unchecked Exception):这类异常的发生多数是因为程序员编写的代码逻辑不够严谨造成的(如数组脚标越界异常),可以选择进行处理或不处理,最好是通过修正、优化代码避免异常的发生(或者使用异常处理简化复杂的逻辑判断代码)。
编译期异常(checked Exception):这类异常一般由程序之外的因素引起的(如程序读取的文件不存在、网络中断),而不是程序员写的代码逻辑有问题,所以程序员容易忽略对这类异常的处理,而恰恰这类异常又很常发生,所以Java要求针对这类可能发生的异常必须进行处理,否则编译无法通过。(只有java语言有需强制处理的异常)
常见的异常示例一: 虚拟机栈内存不足 VirtualMachineError
/*tackOverflowError:虚拟机栈内存不足,无法分配栈帧所需空间。
OutOfMemoryError:没有足够的内存空间可以分配。*/
@Test
public void test01(){
//StackOverflowError
digui();
}
public void digui(){
digui();
}
@Test
public void test02(){
//OutOfMemoryError
//方式一:
int[] arr = new int[Integer.MAX_VALUE];
}
@Test
public void test03(){
//OutOfMemoryError
//方式二:
StringBuilder s = new StringBuilder();
while(true){
s.append("atguigu");
}
}
常见的异常示例一: 运行时异常
@Test
public void test01(){
//NullPointerException
int[] arr=null;
System.out.println(arr.length);
}
@Test
public void test02(){
//ClassCastException
Person p = new Man();
Woman w = (Woman) p;
}
@Test
public void test03(){
//ArrayIndexOutOfBoundsException
int[] arr = new int[5];
for (int i = 1; i <= 5; i++) {
System.out.println(arr[i]);
}
}
@Test
public void test04(){
//InputMismatchException
Scanner input = new Scanner(System.in);
System.out.print("请输入一个整数:");
int num = input.nextInt();
}
@Test
public void test05(){
int a = 1;
int b = 0;
//ArithmeticException
System.out.println(a/b);
}
常见的异常示例一: 编译时异常
@Test
public void test06() throws InterruptedException{
Thread.sleep(1000);//休眠1秒
}
@Test
public void test07() throws FileNotFoundException{
FileInputStream fis = new FileInputStream("Java学习秘籍.txt");
}
@Test
public void test08() throws SQLException{
Connection conn = DriverManager.getConnection("....");
}
Java程序的执行过程中如出现异常,会生成一个异常类对象,然后该异常对象会被提交给Java运行时系统,这个过程称为抛出(throw)异常。异常对象的生成与抛出有两种方式:
由虚拟机自动生成:程序运行过程中,虚拟机检测到程序发生了问题,就会在后台自动创建一个对应异常类的实例对象并自动抛出。
我们通过示例分析下一次产生的过程:
//运行以下程序会产生一个数组索引越界异常ArrayIndexOfBoundsException。
// 工具类
public class ArrayTools {
// 对给定的数组通过给定的角标获取元素。
public static int getElement(int[] arr, int index) {
int element = arr[index];
return element;
}
}
// 测试类
public class ExceptionDemo {
public static void main(String[] args) {
int[] arr = { 34, 12, 67 };
intnum = ArrayTools.getElement(arr, 4)
System.out.println("num=" + num);
System.out.println("over");
}
}
异常产生过程图解:
由此看出,异常对象被JVM创建后,在产生异常的方法中会自动抛出,抛给方法的调用者,抛给main方法,最后抛给虚拟机,虚拟机打印异常信息后终止程序
由开发人员手动创建:Exception exception = new ClassCastException();——创建好的异常对象不抛出对程序没有任何影响,和创建一个普通对象一样,手动创建的异常对象需要手动抛出,才会对程序产生影响。
在Java中,使用关throw关键字手动抛出一个异常对象,throw用在方法内,将这个异常对象传递到方法调用者处,同时结束当前方法的执行。
语法格式:
throw new 异常类名(参数);
示例代码:
public class ThrowDemo {
public static void main(String[] args) {
//创建一个数组
int[] arr = {2,4,52,2};
//根据索引找对应的元素
int index = 4;
int element = getElement(arr, index);
System.out.println(element);
System.out.println("over");
}
/*
* 根据 索引找到数组中对应的元素
*/
public static int getElement(int[] arr,int index){
if(arr == null){
/*
判断条件如果满足,当执行完throw抛出异常对象后,方法已经无法继续运算。
这时就会结束当前方法的执行,并将异常告知给调用者。这时就需要通过异常来解决。
*/
throw new NullPointerException("要访问的arr数组不存在");
}
//判断 索引是否越界
if(index<0 || index>arr.length-1){
/*
判断条件如果满足,当执行完throw抛出异常对象后,方法已经无法继续运算。
这时就会结束当前方法的执行,并将异常告知给调用者。这时就需要通过异常来解决。
*/
throw new ArrayIndexOutOfBoundsException("哥们,角标越界了~~~");
}
int element = arr[index];
return element;
}
}
异常的产生和过程我们了解清楚了,下面我们来学习如何对异常进行处理:
捕获异常:Java中对异常有针对性的语句进行捕获,可以对出现的异常进行指定方式的处理。
try:捕获异常的第一步是用try{…}语句块选定捕获异常的范围,将可能出现异常的代码放在try语句块中。建议:此范围尽量小。
catch:用来进行某种异常的捕获,实现对捕获到的异常进行处理。每个try语句块可以伴随一个或多个catch语句,用于处理可能产生的不同类型的异常。
获取异常信息:
捕获到了异常对象,就可以获取异常对象中封装的异常信息,Throwable类中定义了一些方法用于获取异常对象中的信息:
捕获异常语法如下
try{
编写可能会出现异常的代码
}catch(异常类型1 e){
处理异常的代码
//记录日志/打印异常信息/继续抛出异常
}catch(异常类型2 e){
处理异常的代码
//记录日志/打印异常信息/继续抛出异常
}
....
示例代码:
public class TestException {
public static void main(String[] args) {
try {
readFile("不敲代码学会Java秘籍.txt");
} catch (FileNotFoundException e) {
// e.printStackTrace();
// System.out.println("好好敲代码,不要老是想获得什么秘籍");
System.out.println(e.getMessage());
} catch (IllegalAccessException e) {
e.printStackTrace();
}
System.out.println("继续学习吧...");
}
// 如果定义功能时有问题发生需要报告给调用者。可以通过在方法上使用throws关键字进行声明
public static void readFile(String filePath) throws FileNotFoundException, IllegalAccessException{
File file = new File(filePath);
if(!file.exists()){
throw new FileNotFoundException(filePath+"文件不存在");
}
if(!file.isFile()){
throw new IllegalAccessException(filePath + "不是文件,无法直接读取");
}
//...
}
}
try{
}catch(...){
}finally{
无论try中是否发生异常,也无论catch是否捕获异常,也不管try和catch中是否有return语句,都一定会执行
}
或
try{
}finally{
无论try中是否发生异常,也不管try中是否有return语句,都一定会执行。
}
示例代码: IO流后面也会介绍
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class TestException {
public static void main(String[] args) {
readFile("不敲代码学会Java秘籍.txt");
System.out.println("继续学习吧...");
}
// 如果定义功能时有问题发生需要报告给调用者。可以通过在方法上使用throws关键字进行声明
public static void readFile(String filePath) {
File file = new File(filePath);
FileInputStream fis = null;
try {
if(!file.exists()){
throw new FileNotFoundException(filePath+"文件不存在");
}
if(!file.isFile()){
throw new IllegalAccessException(filePath + "不是文件,无法直接读取");
}
fis = new FileInputStream(file);
//...
} catch (Exception e) {
//抓取到的是编译期异常 抛出去的是运行期
throw new RuntimeException(e);
}finally{
System.out.println("无论如何,这里的代码一定会被执行");
try {
if(fis!=null){
fis.close();
}
} catch (IOException e) {
//抓取到的是编译期异常 抛出去的是运行期
throw new RuntimeException(e);
}
}
}
}
当在try…catch…finally中使用了return会发生什么呢?程序的运行顺序是什么?
形式一:从try回来
public class TestReturn {
public static void main(String[] args) {
int result = test("12");
System.out.println(result);
}
public static int test(String str){
try{
Integer.parseInt(str);
return 1;
}catch(NumberFormatException e){
return -1;
}finally{
System.out.println("test结束");
}
}
}
形式二: 从catch回来
public class TestReturn {
public static void main(String[] args) {
int result = test("a");
System.out.println(result);
}
public static int test(String str){
try{
Integer.parseInt(str);
return 1;
}catch(NumberFormatException e){
return -1;
}finally{
System.out.println("test结束");
}
}
}
形式三:从finally回来
public class TestReturn {
public static void main(String[] args) {
int result = test(“a”);
System.out.println(result);
}
public static int test(String str){
try{
Integer.parseInt(str);
return 1;
}catch(NumberFormatException e){
return -1;
}finally{
System.out.println("test结束");
return 0;
}
}
}
根据上面的案例,我们可以得出,finally虽然最后会执行,但如果finally没有return返回值的时候,方法的返回值是在满足条件的return中,如果finally中return了返回值,那么不论前面return了什么返回值,都会被覆盖掉
// 第一步,定义"根异常":
public class UserException extends RuntimeException {
}
// 第二步,其他异常从”根异常“派生出来:
public class UserExistedException extends UserException {
}
public class UserNotFoundException extends UserException {
}
...
// 第三步,自定义的“根异常”通常提供多个构造方法,直接调用父类的即可:
//用户异常类
public class UserException extends RuntimeException {
public UserException() {
}
public UserException(String message) {
super(message);
}
public UserException(String message, Throwable cause) {
super(message, cause);
}
public UserException(Throwable cause) {
super(cause);
}
}
// 使用
//用户已经存在异常类
public class UserExistedException extends UserException {
public UserExistedException() {
}
public UserExistedException(String message) {
super(message);
}
}
案例演示:
public class DemoUserException {
// 模拟数据库中已存在账号
private static String[] names = {"bill","hill","jill"};
public static void main(String[] args) {
//调用方法
try{
// 可能出现异常的代码
checkUsername("bill");
System.out.println("注册成功");//如果没有异常就是注册成功
}catch(UserExistedException e){
//处理异常
e.printStackTrace();
}
}
//判断当前注册账号是否存在
//因为是编译期异常,又想调用者去处理 所以声明该异常
public static boolean checkUsername(String uname) {
for (int i=0; i<names.length; i++) {
if(names[i].equals(uname)){//如果名字在这里面 就抛出登陆异常
throw new UserExistedException("亲"+uname+"已经被注册了!");
}
}
return true;
}
}
异常处理注意事项
面向对象的基础到此完结,如果学到此时,你觉得还OK,那么恭喜你,你已经成功度过了零基础小白最艰难的时光,后面的学习会相对轻松一些,本章重点对匿名类以及异常进行介绍,学会使用匿名类,可以大大减少我们日常工作中的代码量,也是未来学习大数据的一个基础,异常通常会在捕获后传递到日志文件中,而我们大数据也会经常操作日志文件.所以,每一个知识点都是有联系的…好了,下一期,我会对java中的常用核心类:另外一个引用数据类型–String进行介绍,欢迎大家后台吐槽~