Java基础篇

本文详细介绍了Java的基础知识,涵盖了Java异常处理,包括异常的概念、分类、处理方式以及Throw和Throws的区别。接着讲解了Java反射机制,解释了反射的概念、应用场景和反射API的使用。此外,还探讨了Java注解,包括概念、元注解以及注解处理器的工作原理。文章还讨论了Java内部类、泛型、序列化和对象复制等核心概念,为深入理解Java打下坚实基础。
摘要由CSDN通过智能技术生成


前言

本章介绍Java的基础知识,包括异常、反射、注解等。


一、Java异常

1.1. 概念

当某个方法不能按照正常逻辑继续执行时,可以通过另一种路径退出方法。在这种情况下回抛出一个封装了错误信息的对象。此时,这个方法会立刻退出同时不返回任何值。另外,调用这个方法的其他代码也无法继续执行,异常处理机制会将代码执行交给异常处理器。
在这里插入图片描述

1.2. 异常分类

Throwable是Java语言中所有错误或异常的超类,下一层分为Error和Exception。

  1. Error
    Error类是指Java运行时系统的内部错误或者资源耗尽错误。应用程序不会抛出该类对象。如果出现这样的错误,除了告知用户,剩下的就是尽力使程序安全的终止。
  2. Exception
    Exception分为两个分支,运行时异常RuntimeException和受检异常CheckedException。
  3. RuntimeException:非受检异常,如NullPointException、ClassCastException。它是哪些可能在JVM正常运行期间抛出的异常的超类。如果出现了RuntimeException,那么一定是程序员的错误。
  4. CheckedException:受检异常,如IOException、SQLException。一般是外部错误,这种异常都发生在编译阶段,Java编译器会强制去捕获此类异常,即会出现要求你把这段可能出现异常的程序进行try catch处理。

1.3. 异常的处理方式

异常的处理方式分为两种,直接捕获处理和抛出异常。

  • 捕获处理(try-catch)
    直接在可能抛出异常的代码块,用try-catch包裹,并在catch中对此异常进行处理。
File file = new File("a.txt");
try {
    InputStream in = new FileInputStream(file);
} catch (FileNotFoundException e) {
    e.printStackTrace();
}
  • 抛出异常
    抛出异常是指在当前方法不捕获处理,而是抛出异常给上层调用者处理。抛出异常的方式有以下两种:
    throw关键字:throw new FileNotFoundException();
    throws关键字:public void testCallable() throws ExecutionException, InterruptedException

1.4. Throw和Throws的区别

ThrowThrows
用在函数内,后面跟的是异常对象用在函数声明,后面跟的是异常类
抛出具体的问题对象,执行到throw时,功能已经结束返回,所以不要在该语句下面定义其他语句,因为执行不到用来声明异常,让调用者知道该功能可能会出现问题,可以给出预先的处理方法
执行该代码一定会抛出某种异常表示出现异常的一种可能性,不一定会发生这些异常
都是消极处理异常的方式,只是抛出或可能抛出,但是不会由函数去处理异常,真正处理异常由函数的上层调用处理同左

二、Java反射

2.1. 动态语言

动态语言,是指程序在运行时可以改变其结构,新的函数可以引进,已有的函数可以被删除等结构上的变化。比如常见的JavaScript就是动态语言,除此之外Rubby、Python等也属于动态语言,而C、C++则不属于动态语言。从反射角度说JAVA属于半动态语言。

2.2. 反射机制概念

在Java中的反射机制是指在运行状态中,对于任意一个类都能够知道这个类所有属性和方法;并且对于任意一个对象,都能够调用它的任意一个方法;这种动态获取信息以及动态调用对象方法的功能称为Java语言的反射机制。

2.3. 反射的应用场合

编译时类型和运行时类型
在Java程序中许多对象在运行时都会出现两种类型,编译时类型和运行时类型。编译时的类型由声明对象时实用的类型来决定,运行时的类型由实际赋值给对象的类型决定。
编译时类型无法获取具体方法
程序在运行时还可能接收到外部传入的对象,该对象的编译时类型为Object,但是程序有需要调用该对象的运行时类型的方法。为了解决这些问题,程序需要在运行时发现对象和类的真实信息。然而,如果编译时根本无法预知该对象和类属于哪些类,程序只能依靠运行时信息来发现该对象和类的真实信息,此时就必须使用到反射了。

2.4. Java反射API

反射API用来生成JVM中的类,接口或者对象的信息

  1. Class类:反射的核心类,可以获取类的属性,方法等信息。
  2. Field类:Java.lang.reflec包中的类,表示类的成员变量,可以用来获取和设置类之中的属性值。
  3. Method类:Java.lang.reflec包中的类,表示类的方法,它可以用来获取类中的方法信息或者执行方法。
  4. Constructor类:Java.lang.reflec包中的类,表示类的构造方法。

2.5. 反射使用步骤

  1. 获取想要操作的类的Class对象,它是反射的核心,通过Class对象我们可以任意调用类的方法。
  2. 调用Class类中的方法,即就是反射的使用阶段。
  3. 使用反射API来操作这些信息。

2.6. 获取class对象常用方法

  • 调用某个对象的getClass()方法
Person person = new Person();
Class clazz = person.getClass();
  • 调用某个类型的class属性来获取该类对应的Class对象
Class clazz = Person.class;
  • 使用Class类中的forName()静态方法(最安全、性能最好)
Class clazz = Class.forName("类的全路径");

2.7. 创建对象的常用方法

  • Class对象的newInstance()
    使用Class对象的newInstance方法来创建该类对象时,要求该Class对象对应的类有默认的空构造器。
Class clazz = Class.forName("reflection.Person");
Person p = (Person) clazz.newInstance();
  • 调用Constructor对象的newInstance()
    先使用Class对象获取指定的Constructor对象,再调用Constructor对象的newInstance()方法来创建Class对象对应类的实例,通过这种方法可以选定构造方法创建实例。
Class clazz = Class.forName("reflection.Person");
Constructor c = clazz.getDeclaredConstructor(String.class, String.class, int.class);
Person p = (Person) c.newInstance("张三","男",20);

三、Java注解

3.1. 概念

Annotation(注解)是Java提供的一种对元程序关联信息和与数据的途径和方法。Annotation(注解)是一个接口,程序可以通过反射来获取指定程序中元素的Annotation对象,然后通过该Annotation对象来获取注解中的元数据信息。

3.2. 元注解

元注解的作用是负责注解其他注解。

  • @Target
    说明了Annotation所修饰的对象范围;Annotation可被用于packages、types(类、接口、枚举、Annotation类型)、类型成员(方法、构造方法、成员变量、枚举值)、方法参数和本地变量(如循环变量、catch参数)。在Annotation类型的声明中使用了target可更加明晰其修饰的目标
  • @Retention
    Retention定义了改Annotation的声明周期。表示需要在什么级别保存注解信息,用于描述注解的声明周期,取值(RetentionPoicy)由:
    SOURCE:在源文件中有效
    CLASS:在class文件中有效
    RUNTIME:在运行时有效
  • @Documented
    用于描述其他类型的annotation应该被作为标注的程序成员的公共API,因此可以被例如javadoc此类的工具文档化。
  • @Inherited
    该注解是一个标记注解,阐述了某个被标注的类型是可以被继承的。如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类。

3.3. 注解处理器

如果没有用来读取注解的方法和工作,那么注解也就不会比注释更有用处了。使用注解的过程中,很重要的一部分就是创建于使用注解处理器。Java SE5扩展了反射机制的API,以帮助程序员快速的构造自定义注解处理器。

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FruitInfo {

    int id() default -1;
    String name() default "";
    String address() default "";
}

public class Apple {

    @FruitInfo(id = 1, name = "苹果", address = "菜市场")
    private String appleProvider;

    public String getAppleProvider() {
        return appleProvider;
    }

    public void setAppleProvider(String appleProvider) {
        this.appleProvider = appleProvider;
    }
}

public class FruitInfoUtil {

    public static void getFruitInfo(Class<?> clazz) {
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            if (field.isAnnotationPresent(FruitInfo.class)) {
                FruitInfo fruitInfo = field.getAnnotation(FruitInfo.class);
                System.out.println("水果名称:" + fruitInfo.name() + ",水果售卖地址:" + fruitInfo.address());
            }
        }
    }

    public static void main(String[] args) {
        Apple apple = new Apple();
        getFruitInfo(apple.getClass());
    }
}

在这里插入图片描述

四、Java内部类

Java类中不仅可以定义变量和方法,还可以定义类,这样定义在类内部的类就被称为内部类。根据定义的方式不同,内部类分为静态内部类、成员内部类、局部内部类、匿名内部类。

4.1. 静态内部类

定义在类内部的静态类,就是静态内部类。

public class Out {

    private static int a;

    public static class Inner {
        public void print() {
            System.out.println(a);
        }
    }
}
  1. 静态内部类可以访问外部类所有的静态变量和方法,即使是private的也一样。
  2. 静态内部类和一般类一致,可以定义静态变量、方法、构造方法等。
  3. 其他类使用静态内部类需要使用“外部类.静态内部类”方法,如下所示:
Out.Inner inner = new Out.Inner();
inner.print();
  1. Java集合类HashMap内部就有一个静态内部类Entry。Entry是HashMap存放元素的抽象,HashMap内部维护Entry数组用了存放元素,但是Entry对使用者是透明的。像这种和外部类关系密切的,且不依赖外部类实例的,都可以使用静态内部类。

4.2. 成员内部类

定义在类内部的非静态类,就是成员内部类。成员内部类不能定义静态方法和变量(final 修饰的除外)。这是因为成员内部类是非静态的,类初始化的时候先初始化静态成员,如果允许成员内部类定义静态变量,那么成员内部类的静态变量初始化顺序是有歧义的。

public class Out {

    private static int a;

    public class Inner {
        public void print() {
            System.out.println(a);
        }
    }
}

4.3. 局部内部类

定义在方法中的类,就是局部类。如果一个类只在某个方法中使用,则可以考虑使用局部类。

public class Out {

    private static int a;

	public void test({
		final int d=1;
		class Inner {
	        public void print() {
	            System.out.println(a);
	        }
	    }	
	}
}

4.4. 匿名内部类

匿名内部类我们必须要继承一个父类或者实现一个接口,当然也仅能只继承一个父类或者实现一个接口。同时它也是没有class关键字,这是因为匿名内部类是直接使用new来生成一个对象的引用。

public abstract class Bird {
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public abstract int fly();
}

public class BirdTest {

    public void test(Bird bird) {
        System.out.println(bird.getName() + "能够飞" + bird.fly() + "米");
    }

    public static void main(String[] args) {
        BirdTest birdTest  = new BirdTest();
        birdTest.test(new Bird() {
            @Override
            public String getName() {
                return "大雁";
            }

            @Override
            public int fly() {
                return 10000;
            }
        });
    }
}

在这里插入图片描述

五、Java泛型

泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。泛型的本质是参数化类型,也就是说所操作的数据类型被指定作为一个参数。

5.1. 泛型方法

你可以写一个泛型方法,该方法在调用时可以接收不同类型的参数。根据传递给泛型方法的参数类型,编译器适当地处理每一个方法调用。

public static <E> void printArray(E[] inputArray) {
    for (E element : inputArray) {
        System.out.println(element);
    }
}

5.2. 泛型类

泛型类的声明和非泛型类的声明类似,除了在类名后面添加了类型参数声明部分。和泛型方法一样,泛型类的类型参数声明部分也包含一个或多个类型参数,参数间用逗号隔开。一个泛型参数,也被称为一个类型变量,是用于指定一个泛型类型名称的标识符。因为他们接受一个或多个参数,这些类被称为参数化的类或参数化的类型。

public class Box<T> {
    
    private T t;

    public T getT() {
        return t;
    }

    public void setT(T t) {
        this.t = t;
    }
}

5.3. 类型通配符

类型通配符一般是使用?代替具体的类型参数,例如List<?>在逻辑上是List,List等所有List<具体类型实参>的父类。

5.4. 类型擦除

Java中的泛型基本上都是编译器这个层次来实现的。在生成的Java字节代码中不是不包含泛型中的类型信息的。使用泛型的时候加上的类型参数,会被编译器在编译的时候去掉。这个过程就称为类型擦除。如代码中定义的List和List等类型,在编译之后都会变成List。JVM看到的只是List,而由泛型附加的类型信息对JVM来说是不可见的。
类型擦除的基本过程也比较简答,首先是找到用来替换类型参数的具体类。这个具体类一般是Object。如果指定了类型参数的上界。把代码中的类型参数都替换成具体的类。


六、Java序列化

6.1. 保持(持久化)对象及其状态到内存或者磁盘

Java平台允许我们在内存中创建可复用得Java对象,但一般情况下,只有当JVM处于运行时,这些对象才有可能存在,即这些对象得生命周期不会比JVM得生命周期更长。但在现实应用中,就可能要求在JVM停止运行之后能够保存(持久化)指定对象,并在将来重新读取被保存得对象。Java对象序列化就能够帮助我们实现该功能。

6.2. 序列化对象以字节数组保持-静态成员不保存

使用Java对象序列化,在保存对象时,会把其状态保存为一组字节,在未来,再将这些字节组装成对象。必须注意的是,对象序列化保存的是对象的“状态”,即它的成员变量。由此可知,对象序列化不会关注类中的静态变量。

6.3. 序列化用户远程对象传输

除了在持久化对象时会用到对象序列化之外,当使用RMI(远程方法调用),或在网络中传递对象时,都会用到对象序列化。Java序列化API为处理对象序列化提供一个标准机制,该API简单易用。

6.4. Serializable实现序列化

在Java中,只要一个类实现了java.io.Serializable接口,那么它就可以被序列化。

6.5. ObjectOutputStream和ObjectInputStream对对象进行序列化和反序列化

通过ObjectOutputStream和ObjectInputStream对对象进行序列化和反序列化

6.6. writeObject和readObject自定义序列化策略

在类中增加writeObject和readObject方法可以实现自定义序列化策略。

6.7. 序列化ID

虚拟机是否允许反序列化,不仅取决于类路径和功能代码是否一致,一个非常重要的一点是两个类的序列化ID是否一致。

6.8. 序列化并不保存静态变量

序列化子父类说明
要想将父类对象也序列化,就需要让父类也实现Serializable接口。
Transient关键字阻止该变量被序列化到文件中
1、在变量声明前加上Transient关键字,可以阻止该变量被序列化到文件中,在被反序列化后,transient变量的值设为初始值,如int型是0,对象型是null。
2、服务端给客户端发送序列化对象数据,对象中有一些数据是敏感的,比如密码字符串等,希望对该密码字段在序列化时,进行加密,而客户端如果拥有解密的密钥,只有在客户端进行反序列化时,才可以对密码进行读取,这样可以一定程度保证序列化对象的数据安全。

七、Java复制

将一个对象的引用复制给另外一个对象,一共有三种方式。第一种方式是直接赋值,第二种方式是浅拷贝,第三种是深拷贝。所以大家都知道,这三种概念实际上都是为了拷贝对象。

7.1. 直接赋值

直接赋值。在Java中,A a1 = a2,我们需要理解的是这实际上复制的是引用,也就是说a1和a2指向的是同一个对象的地址。因此,当a1变化时,a2里面的成员变量也会跟着变化。

7.2. 浅复制

创建一个新对象,然后将当前对象的非静态字段复制到该新对象,如果自动是值类型的,那么对该字段执行复制;如果该字段是引用类型的话,则复制引用但不复制引用的对象。因此,原始对象及其副本引用同一个对象。

class Resume implement Cloneable {
	public Object clone() {
		try {
			return (Resume) super.clone();
		} catch (Exception e) {
			return null;
		}
	}
}

7.3. 深复制

深拷贝不仅复制对象本身,而且复制对象包含的引用指向的所有对象。

class Student implement Cloneable {
	String name;
	int age;
	Professor p;
	Student(String name, int age, Professor p) {
		this.name = name;
		this.age = age;
		this.p = p;
	}

	public Object clone() {
		Student o = null;
		try {
			o = (Student) super.clone();
		} catch (CloneNotSupportedException e) {
			System.out.println(e.toString());
		}
		o.p = (Professor) p.clone;
		return o;
	}
}

7.4. 序列化

在Java语音里深复制一个对象,常常可以先使对象实现Serializable接口,然后把对象(实际上只是对象的一个拷贝)写到一个流里,在从流里读出来,便可以重建对象。

总结

以上就是Java基础知识。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值