Java基础知识点(三)

一、Java的IO和NIO

1. Java NIO提供了与标准IO不同的IO工作方式

  • Channels and Buffers(通道和缓冲区):标准的IO基于字节流和字符流进行操作的,而NIO是基于通道(Channel)和缓冲区(Buffer)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中;
  • Asynhronous IO(异步IO):Java NIO可以异步地使用IO,例如:当线程从通道读取数据到缓冲区时,线程还可以进行其他事情。当数据被写入到缓冲区时,线程可以继续处理它,从缓冲区写入通道也类似;
  • Selector(选择器):Java NIO引入了选择器的概念,选择器用于监听多个通道的事件(比如:连接打开,数据到达),因此单个线程可以监听多个数据通道。

2. 使用场景

NIO

  • 优势在于一个线程管理多个通道;但是数据处理将变得复杂;
  • 如果需要管理同时打开的成千上万个连接,这些连接每次只是发送少量的数据,采用这种;

传统IO

  • 适用于一个线程管理一个通道的情况,因为其中的流数据读取是阻塞的;
  • 如果需要管理同时打开不太多的连接,这些连接会发送大量的数据,使用这种。

3. NIO和IO的区别

理念上的区别:NIO将阻塞交给了后台线程执行;

(1)IO是面向流的,NIO是面向缓冲区的

Java IO面向流,意味着每次从流中读取一个或多个字节,直至读取所有字节,他们没有被缓存在任何地方。此外,它不能前后移动流中的数据。如果需要前后移动从流中读取的数据,需要先将它缓存到一个缓冲区;

Java NIO的缓冲导向方法略有不同,读取数据到一个它稍后处理的缓冲区,需要时可以在缓冲区中前后移动数据。这就增加了处理过程中的灵活性。但是,还需要检查是否该缓冲区中包含所有需要处理的数据,而且需要确保当更多的数据读入缓冲区时,不要覆盖缓冲区里尚未处理的数据。

(2)IO流是阻塞的,NIO是不阻塞的

从名称上看,NIO的N有两种含义:New IO和Non-blocking IO

Java IO的各种流是阻塞的,这意味着,当一个线程调用read()或者write()时,该线程被阻塞了,直到有一些数据被读取,或者数据完全写入,该线程在此期间不能再干任何事情;

Java NIO的非阻塞模式,使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取;NIO可以让使用者只使用一个(或多个)单线程管理多个通道(网络连接或文件),但付出的代价是解析数据可能比从一个阻塞流中读数据更复杂;非阻塞写也是如此,一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。

(3)选择器

Java NIO的选择器允许一个单独的线程来监视多个输入通道,可以注册多个通道使用一个选择器,然后使用一个单独的线程来“选择”通道:这些通道里面已经有可以处理的输入,或者选择已准备写入的通道。这种选择机制,使得一个单独的线程很容易来管理多个通道。

二、Java反射

参考资料

Java 反射详解

1. 什么是反射

Java反射就是在运行状态中,对于任意一个类,都能够知道这个类所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;并且能改变它的属性。而这也是Java被视为动态(或准动态,因为一般而言动态语言定义是程序运行时,允许改变程序结构或变量类型,这种语言成为动态语言,从这个观点看,Perl、Python、Ruby是动态语言,C++、Java、C#不是动态语言)语言的一个关键性质。

2. 反射能做什么

反射机制允许程序在运行时获得任何一个已知名称的class的内部信息,包括其modifiers(修饰符)、fields(属性)、methods(方法)等,并可于运行时改变fields内容或调用methods。这样便可以更灵活地编写代码,代码可以在运行时装配,无需再组件之间进行源代码链接,降低代码的耦合度;还有动态代理的实现等等;但是需要注意反射使用不当会造成很高的资源消耗。

3. 反射的具体实现

举例,一个基本类Person:

public class Person {
    //私有属性
    private String name = "Tom";
    //公有属性
    public int age = 18;
    //构造方法
    public Person() {
    }
    //私有方法
    private void say(){
        System.out.println("private say()...");
    }
    //公有方法
    public void work(){
        System.out.println("public work()...");
    }
}

(1)得到Class的三种方式

  • 通过对象调用getClass()方法获取,通常应用在:比如传过来一个Object类型对象,而我们不知道具体是什么类,就用这种方法:

    Person p1 = new Person();
    Class c1 = p1.getClass();
    
  • 直接通过类名.class的方式得到,该方法最为安全可靠,程序性能更高,这说明任何一个类都有一个隐含的静态成员变量 class

    Class c2 = Person.class;
    
  • 通过Class类的静态方法 forName() 来获取,用的最多,但可能抛出 ClassNotFoundException 异常

    Class c3 = Class.forName("com.ys.reflex.Person");
    

需要注意的是:一个类再JVM中只会有一个Class实例,即我们如果对上面的c1 c2 c3进行equals比较,结果都是true。

(2)通过Class类获取成员变量、成员方法、接口、超类、构造方法等

查阅 API 可以看到 Class 有很多方法:

getName():获得类的完整名字

getFields():获得类的public类型的属性,包括从父类继承来的

getDeclaredFields():可以获取本类所有的属性,包括private的,但是不能获取继承来的字段。 (注: 这里只能获取到private的字段,但并不能访问该private字段的值,除非加上setAccessible(true))

getMethods():所有公用(public)方法包括其继承类的公用方法,当然也包括它所实现接口的方法

getDeclaredMethods():获得类的所有方法。包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法。当然也包括它所实现接口的方法

getMethod(String name, Class[] parameterTypes):获得类的特定方法,name参数指定方法的名字,parameterTypes 参数指定方法的参数类型

getConstructors():获得类的public类型的构造方法

getConstructor(Class[] parameterTypes):获得类的特定构造方法,parameterTypes 参数指定构造方法的参数类型

newInstance():通过类的不带参数的构造方法创建这个类的一个对象

举例演示:

//获得类完整的名字
String className = c2.getName();
System.out.println(className);//输出com.ys.reflex.Person

//获得类的public类型的属性。
Field[] fields = c2.getFields();
for(Field field : fields){
   System.out.println(field.getName());//age
}

//获得类的所有属性。包括私有的
Field [] allFields = c2.getDeclaredFields();
for(Field field : allFields){
    System.out.println(field.getName());//name    age
}

//获得类的public类型的方法。这里包括 Object 类的一些方法
Method [] methods = c2.getMethods();
for(Method method : methods){
    System.out.println(method.getName());//work waid equls toString hashCode等
}

//获得类的所有方法。
Method [] allMethods = c2.getDeclaredMethods();
for(Method method : allMethods){
    System.out.println(method.getName());//work say
}

//获得指定的属性
Field f1 = c2.getField("age");
System.out.println(f1);
//获得指定的私有属性
Field f2 = c2.getDeclaredField("name");
//启用和禁用访问安全检查的开关,值为 true,则表示反射的对象在使用时应该取消 java 语言的访问检查;反之不取消
f2.setAccessible(true);
System.out.println(f2);

//创建这个类的一个对象
Object p2 =  c2.newInstance();
//将 p2 对象的  f2 属性赋值为 Bob,f2 属性即为 私有属性 name
f2.set(p2,"Bob");
//使用反射机制可以打破封装性,导致了java对象的属性不安全。
System.out.println(f2.get(p2)); //Bob

//获取构造方法
Constructor [] constructors = c2.getConstructors();
for(Constructor constructor : constructors){
    System.out.println(constructor.toString());//public com.ys.reflex.Person()
}

4. 根据反射获取父类属性

父类:

public class Parent {
    public String publicField = "parent_publicField";
    protected String protectField = "parent_protectField";
    String defaultField = "parent_defaultField";
    private String privateField = "parent_privateField";

}

子类:

public class Son extends Parent {
}

测试:

public class ReflectionTest {

    @Test
    public void testGetParentField() throws Exception{
        Class c1 = Class.forName("com.ys.model.Son");
        //获取父类私有属性值
        System.out.println(getFieldValue(c1.newInstance(),"privateField"));
    }

    public static Field getDeclaredField(Object obj,String fieldName) {
        Field field = null;
        Class c = obj.getClass();
        for(; c != Object.class ; c = c.getSuperclass()){
            try {
                field = c.getDeclaredField(fieldName);
                field.setAccessible(true);
                return field;
            }catch (Exception e){
                //这里甚么都不要做!并且这里的异常必须这样写,不能抛出去。
                //如果这里的异常打印或者往外抛,则就不会执行c = c.getSuperclass(),最后就不会进入到父类中了
            }
        }
        return null;
    }
    public static Object getFieldValue(Object object,String fieldName) throws Exception{
        Field field = getDeclaredField(object,fieldName);

        return field.get(object);
    }
}

通过执行上述代码,可以获得父类的私有属性值,需要注意的是直接通过反射获取子类的对象是不能得到父类属性值的,必须根据反射获得子类Class对象再调用getSuperclass()方法获取父类Class对象,然后再通过父类Class对象获取父类属性值。

5. 反射总结

Java反射就是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能调用它的任意方法和属性;并且能改变它的属性。

结合JVM类加载过程:

Class.forName和ClassLoader.loadClass()区别

Class.forName除了将类的.class文件加载到JVM之中,还会对类进行解析,执行类中的static块;而classloader只干一件事情,就是将.class文件加载到JVM中,不会执行static块,只有在newInstance()时才会执行static块。forName("”)得到的class是已经初始化完成的;

最重要的区别是forName会初始化Class,而loadClass不会,因此如果要求加载时类的静态变量被初始化或静态块的代码被执行就只能用forName,而用loadClass只有等创建类实例的时候才会进行这些初始化。

Class.forName(className)方法,内部实际调用的方法是  Class.forName(className,true,classloader);

第2个boolean参数表示类是否需要初始化,Class.forName(className)默认是需要初始化。一旦初始化,就会触发目标对象的 static块代码执行,static参数也也会被再次初始化。

    
ClassLoader.loadClass(className)方法,内部实际调用的方法是  ClassLoader.loadClass(className,false);

第2个 boolean参数,表示目标对象是否进行链接,false表示不进行链接,由上面介绍可以,不进行链接意味着不进行包括初始化等一些列步骤,那么静态块和静态对象就不会得到执行

灵活使用反射能让我们代码更加灵活,比如JDBC原生代码注册驱动,Hibernate的实体类,Spring的AOP等都有反射的实现,但反射的缺点在于消耗系统性能,增加复杂性。

三、final关键字

用来修饰数据,包括成员变量和局部变量,该变量只能被赋值一次且它的值无法改变;

对于成员变量来讲,修饰的类变量,必须在声明时初始化;修饰的实例变量,必须在声明时或者构造方法中对它赋值;

用来修饰方法参数,表示在变量的生存周期中它的值不能被改变;

修饰类,表示该类无法被继承。

四、ArrayList和LinkedList

(1)ArrayList是实现了基于动态数组的数据结构,LinkedList是基于链表的数据结构;

(2)对于随机访问get和set,ArrayList优于LinkedList,因为LinkedList需要移动指针;

(3)对于增加和删除add和remove操作,LinkedList优于ArrayList,因为ArrayList要移动数据。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值