JAVA基础学习(三)——反射、注解、泛型、集合、IO及异常等

书接上文。

目录

前言

一、反射

1、什么是反射?

2、反射的管理

3、动态代理

二、注解

三、泛型

四、集合

1、列表

2、哈希表

3、队列

4、栈

5、其他

五、IO

1、常见的输入输出

2、文件内容的输入输出

六、异常

总结


前言

本文汇总一下杂七杂八的知识,重要程度会在文中表述。


一、反射

1、什么是反射?

        在学golang时候接触过反射,但并没有看懂,这里温习一下。

        现在给你一个类Object,要获取他的字段,方法,父类等信息,如何做呢?知道这个类是啥,求上述信息很简单,但万一不知道呢?在测试中可能就会出现这种情况。反射就是程序在运行期间,获取对象的各种信息的过程。换句话说,根据class实例,获取class各种信息的过程就是反射。

        任何一个类class的实例都是由JVM动态加载实现的。JVM为每一个加载的class创建了对应的class实例,并管理所有信息包括类名、包名、字段、方法等。这里的动态加载是说JVM会在程序使用到某个class时候才会加载实现类。

2、反射的管理

        如何获取一个类的实例呢?直接获取Class cls = String.class;实例变量获取String s="Hello";Class cls = s.getClass;类完整名获取Class cls = Class.forName("java.lang.String");

        接着,如何获取类的字段呢?Field getField(name):根据字段名获取当前类和父类的字段信息;Field getDeclaredField(name):根据字段名获取当前类的字段信息;Field[] getFields():获取当前类和父类所有字段信息;Field[] getDeclaredFields():获取当前类的所有字段信息。

        对于获取到的字段,使用f.getName(),f.getType()分别得到字段名字与类型。使用int m = f.getModifiers(),然后Modifier.isPublic(m)/isPrivate(m)可以实现对类型的判断。注意,要想获取当前类的私有字段,可以先设置f.setAccessible(true);再进行获取。不过JVM中安全检查的存在可能导致上述私有字段访问失效。设置字段值可以使用f.set(p,"...")进行设置。要注意的是,对字段读写会破坏字段的封装,谨慎使用。

        接着如何获取类的方法?与字段是一样的思考方式。首先使用四个函数getMethod() getMethods() getDeclaredMethod() getDeclaredMethods()获取方法实例;对于获取的方法实例可以使用getName(),getReturnType(),getParameterTypes(),getModifiers(),可以使用Object invoke(Object instance, Object... parameters)来调用方法,setAccessible(true)来访问非公开方法。

        如何获取类的构造方法呢?同样的思路,四个函数分别是getConstructor(),getConstructors(),getDeclaredConstructor(),getDeclaredConstructors();通过构造来创建实例newInstance();通过setAccessible(true)访问非公开构造方法。

        如何获取继承关系?Class getSuperclass();实现当前类的所有接口使用Class[] getInterfaces()。

3、动态代理

        所有接口类型不能实例化,如何不实现接口类,而是在运行期间创建某个实例呢?可以的,使用动态代理方法。过程:定义InvocationHandler实例,负责实现接口方法调用;Proxy.newProxyInstance()创建interface实例,三个参数是接口类class.getClassLoader、需要实现的接口new Class[]{Hello.class}、接口方法实例;强制转化Object为接口。调用接口即可。

        这部分涉及代理模式,动态代理的作用网上的段子就解释得很好:我们通常使用class是直接定义,再装载到JVM上,运行时可以查到所有的信息,就像入学时每个人都需要登记一样;但是总有些情况,不需要知道类,也不能知道类的信息,打架雇人,就需要代理模式,仅仅实现接口——一堆操作的集合,不需要知道委托人的具体信息,只需要“时间地点”。这样来降低风险…… 

二、注解

        Java又被戏称为“面向注解编程”,也是不无道理。注解用于简化开发,举个简单例子,@bean注解放在一个类的上方,在编译时会自动生成get\set方法,简化开发,Spring、Springboot中常会使用,这个建议大家在写业务代码时学习,不然背是背不住的。

三、泛型

        这个是非常常见的概念了。当我们定义一个集合时,集合的元素可能是各种各样的类型,因此需要各种各样的集合类来实现。这样太麻烦了,于是乎,使用模板来表述同一类型但不同元素类型的集合。

        使用泛型时,需要把泛型参数<T>转换成需要的class类型,List<String>list=new ArrayList<>();不指定泛型参数类型,只会转换成Object类型。JAVA中通过擦拭法实现泛型,导致编译器把类型<T>视为Object,根据<T>实现安全的强制转型。因此,擦拭法决定了:泛型不能是基本类型,不能获取带泛型类型的class,不能判断带泛型类型的类型,不能实例化泛型类型。

        泛型的继承:一个类可以继承自泛型类,注意,<? extends Number>通配符作为方法参数时表示:调用Number的方法,只能读出,不能写入。<? super Integer>表示super通配符只能写不能读。

        注意部分反射类型就是泛型,比如Class<T>,不这样做就只能强制转型。

四、集合

        与大小固定的数组相比,集合能容纳更多类型的数据,能实时扩容,因此适用范围更广。这里只简单介绍常用类型与对应函数,需要大量做题才能更好领悟。

1、列表

        链表有两种,一种是ArrayList,可自动扩容,实现增删改查,效率更高,List<String> list = new ArrayList<>();类似于golang中slice;另一种是LinkedList,即链表。

        接口方法包括:在末尾添加一个元素add("a");在指定索引后加上元素add(0,"a");删除指定索引元素remove(0);删除某个元素remove("a")获取指定索引元素get(0);获取元素个数size()

        快速创建List<Integer> list=List.of(1,2,5);

        遍历for(Integer n:list)或者for(Iterator<Integer>it=list.iterator();it.hasNext();){Integer i=it.next();...}

        列表与数组的转换:Integer[] array = list.toArray(Integer[]::new)

List<Integer> list = List.of(array);

        注意:判断列表是否相等,一定是判断其中元素是否equals()!!!

2、哈希表

        键值映射表:put放入元素,get获取元素,containsKey判断元素是否存在

        遍历:for(String key:map.keySet())或者for(Map.Entry<String,Integer>entry : map.entrySet())

        枚举类型map使用EnumMap,对于想要put顺序和输出顺序一致的情况,可以使用TreeMap!!!

        配置Map——Properties:在配置文件中,常为String:String类型比如User:Tung,因此可以使用Properties,首先定义文件config.properties,然后创建实例Properties props = new Properties();load加载文件props.load(new java.io.FileInputStream(“config.properties”));getProperty("User")读取配置

        Set只存储不重复的key,包含add\remove\contains等方法。

3、队列

        先进先出的结构:获取长度使用size();注意!!!其余情况有两种标准——添加元素到队尾add/offer、获取队首元素并从队列中删除remove/poll、获取队首元素但不从队列中删除element/peek,前者一旦不成功会抛异常,后者只会返回FALSE!!!

        优先队列:

        队列中的元素是按照一定顺序排列的,Queue<User> q = new PriorityQueue<>(new UserComparator());比较方法为class UserComparator implements Comparator<User> {
    public int compare(User u1, User u2) {...}...}

        双端队列:顾名思义,两段都可以出入,因此需要加上LAST与FIRST——添加元素到队尾addLast/offerLast、添加元素到队首addFirst/offerFirst、取队尾元素并删除removeLast/pollLast、取队首元素并删除removeFirst/pollFirst 、取队尾元素不删除getLast/peekLast、取队首元素不删除getFirst/peekFirst。      

4、栈

        先进后出的结构,常用方法有push\pop\peek等

5、其他

        如Collections,作用有创建空集合、单元素集合、不可变集合、排序等操作。一般都可以通过对应集合来实现,了解即可。

五、IO

1、常见的输入输出

        我们在写ACM代码时候,需要手动输入输出,这里就简单介绍一下:

        首先处理单个输入:

//输入两行数据,第一行是数组长度,第二行是数组元素
Scanner scanner = new Scanner(System.in);
int N = 100010;
int[] w = new int[N];

int n = scanner.nextInt(); 
for (int i = 0; i < n; i++) {
    w[i] = scanner.nextInt(); 
}

//如果是字符串,使用next()/nextLine()
if (scan.hasNext()) {
    String str1 = scan.next();
    System.out.println("输入的数据为:" + str1);
}
//nextLine方法返回的是Enter键之前的所有字符,它是可以得到带空格的字符串的。
//next会自动消去有效字符前的空格,只返回输入的字符,不能得到带空格的字符串。

        注意如果数据用,隔开1,2,3,4,5这种,需要用Scanner scanner = new Scanner(System.in).useDelimiter(",|\\s+");正则\\s+代表空格或换行符等等。

        多组输入也是一样的,只不过多了循环罢了。

2、文件内容的输入输出

        常用的两种方法,一种是字节流,一种是字符流。

//字节流,最好使用try(resource)保证流正确关闭
public void readFile() throws IOException {
    InputStream input = new FileInputStream("src/readme.txt");
    for (;;) {
        int n = input.read();
        if (n == -1) {
            break;
        }
        System.out.println(n);
    }
    input.close(); 
}
public void writeFile() throws IOException {
    OutputStream output = new FileOutputStream("out/readme.txt");
    output.write(72); 
}

//字符流,以char为单位
public void readFile() throws IOException {
    Reader reader = new FileReader("src/readme.txt");
    for (;;) {
        int n = reader.read(); 
        if (n == -1) {
            break;
        }
        System.out.println((char)n); 
    }
    reader.close(); 
}
public void writeFile() throws IOException {
    Writer output = new FileWriter("out/readme.txt");
    output.write(72); 
}

六、异常

        异常继承自Throwable,包含error、Exception两种。前者代表严重错误,程序无能为力——内存耗尽、栈溢出等;后者是运行时的错误,可以捕获处理。

        Exception类有两大类,RuntimeException和其他(IOException等)。非RuntimeException必须强制捕获!!!其他可以不用强制捕获

        捕获时常使用try...catch:

try {
    byte[] bs = toGBK("中文");
    System.out.println(Arrays.toString(bs));
} catch (UnsupportedEncodingException e) {
    System.out.println(e);
}

        还可以在后面加上finally代表无论是否有异常都会执行的语句。

        如何抛异常呢?throw new XXXException();

        大型业务中,要学会定义异常类BaseException,继承自RuntimeException,然后其他异常继承这个BaseException。

        最为常见的异常是NullPointerException,空指针(引用)异常。

        最后说一下,业务中一般可以通过log文件排查错误。比如Log4j,有四个优先级debug、info、warning、error、fatal、off,有三大组件Logger输出不同日志级别、Appender输出指定地点、Layout输出一定格式。

        定义static final Logger logger =  Logger.getLogger(LogTest.class);

        使用logger.debug("...");logger.error("...");


总结

这一部分的内容繁琐又枯燥,只能在实际业务开发、代码练习中加强学习。

  • 53
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值