Java底层知识:反射、IO

反射

说明

反射用于运行时:

①获取类的字段、方法

②调用对象的字段、方法。(包含私有的,可以通过设置方法或属性为可见,然后调用)

class

说明:其是所有对象的元数据,在jvm只有一份,存放在常量池中,其内部有类的属性、方法,其就是类的编译后的机器码,C、C++代码。

三种方式得到class(都返回Class)

①Object.class

​ 由于要导入依赖,违反尽量解耦的思想,所以不怎么用。

②Object.getClass()

​ 由于已经有了对象,所以该方法通常也无用。

③Class.forName()

最常用的方法,用于反射得到类

然后:通过class可以得到类的属性、方法,如spring的xml配置中,可以通过属性,得到类的全限定名,随即使用Class.forName来动态加载类。

class类的常用方法

实例:windows:E:\study\java\eclipse\testSet

概括

  • 使用getXXX来获得(包含继承的)所有公有字段、方法。
    • 构造函数除外,其不会显示继承自父类的构造函数。
  • 使用getDeclaredXXX来获得(不包含继承的)所有字段、方法:公有、私有、保护、默认。
  • 私有字段、方法若要获取、修改、调用,需要先暴力解除限制。(设置可访问为true)

①构造函数:

获取

1-获取所有公共构造函数(不包含继承的构造函数):Constructor[] getConstructors();

2-获取所有构造函数(公共、私有、保护、默认,不包含继承的):

Constructor[] getDelclaredConstructors();

3-获取单个构造函数:

Constructor getConstructor(Class...)/getDeclaredConstructor(Class...)

调用

Constructor.newInstance(Object... initArgs);
说明:其是先实例化了构造方法所在类,得到对象然后调用该方法,并传入初始化参数

​ 如果是私有的构造函数,需要先设置私有构造函数的访问权限为true:setAccessible(true);(暴力解除限制)

②成员变量

获取

1-获取所有公共字段(包含继承字段):Field[] getFields(); (注意:应考虑没有公有字段时的逻辑)

2-获取所有字段(公有、保护、私有、默认;不包含继承字段):Field[] getDeclaredFields();

3-获取单个字段(根据字段名字):

Field getField(String)\getDeclaredField(String)

调用

1-首先实例化对象:Class.getConstructor.newInstance();

2-私有字段需暴力接触限制:Field.setAccessible(true);

3-设置字段:Filed.set(Object, value); //Object是准备设置的对象,即上面实例化的对象变量, //value为设置的值

4-通过调用字段get方法,并标准输出到console进行验证。

③成员方法

获取

1-获取所有公有方法(包含继承来的方法):Method[] getMethods(); (注意:应考虑没有公有方法时的逻辑)

2-获取所有方法(公有、保护、私有、默认;不包含继承的方法):Method[] getDeclaredMethods();

3-获取单个方法(根据方法名+参数的类型):

Method getMethod (String name, Class...)\getDeclaredMethod (String name, Class...)

调用

1-首先实例化对象:Class.getConstructor.newInstance();

2-私有方法需暴力接触限制:Method.setAccessible(true);

3-调用方法:Method.invoke(Object, Class...); //Object是准备调用的对象,即上面实例化的对 //象变量, Class...为可变参数值

4-验证:

例如设置了一个字段后,可以通过Method.invoke(...),来调用getField()调用一个对象的方法;(Method:Class.getDeclaredMethod("getField") )

也可以直接调用对象方法:Obj.getField

补充

main方法反射调用

说明:main也可以反射调用,其步骤与普通的静态成员变量相同:

​ invoke中,第一个参数(对象实例)为null;第二个是String数组,但由于new String[]{}会被拆成三个对象,所以需要强转成Object。

    //1、获取Student对象的字节码
    Class clazz = Class.forName("fanshe.main.Student");
            
    //2、获取main方法
    //第一个参数:方法名称,第二个参数:方法形参的类型,
     Method methodMain = clazz.getMethod("main", String[].class);

    //3、调用main方法
    // methodMain.invoke(null, new String[]{"a","b","c"});
    //第一个参数,对象类型,因为方法是static静态的,所以为null可以,第二个参数是String数组,这里要注意在jdk1.4时是数组,jdk1.5之后是可变参数
    //这里拆的时候将  new String[]{"a","b","c"} 拆成3个对象。。。所以需要将它强转。
    //方式一
    methodMain.invoke(null, (Object)new String[]{"a","b","c"});
    //方式二
    // methodMain.invoke(null, new Object[]{new String[]{"a","b","c"}});

使用property文件

    // 测试Class的成员方法:获取、调用。
    public static void testClassMainMethod() throws ClassNotFoundException, NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, IOException {
    //①加载指定类的.class文件
    Class clazz = Class.forName(getValue("qualifiedName"));
    //②通过class,得到main方法
    Method mainMethod = clazz.getDeclaredMethod(getValue("method"), String[].class);    
    //③调用mian方法,并输出测试
    //方法一
    mainMethod.invoke(null, (Object)new String[]{"aa","bb","cc"});
    //方法二
    mainMethod.invoke(null, new Object[] {new String[] {"aa2", "bb2", "cc2"}});
    }
    
    /**
     * 用于解析property文件,并根据传入的key,得到对应value
     * @param key 传入在文件中,定义的key
     * @return String 返回对应key的property
     * @throws IOException
     */
    public static String getValue(String key) throws IOException {
        FileReader in = new FileReader("properties.txt");
        Properties pros = new Properties();
        pros.load(in);
        in.close();
        return pros.getProperty(key);
    }

使用反射越过泛型检查

如下,通过数组的class,得到add()方法,并添加一个浮点数到了char数组中,越过了泛型检查。

原因:泛型检查在编译后会被擦去,而invoke是在运行时调用,此时泛型已擦去。

    public static void genericsTest() throws Exception{
        ArrayList strList = new ArrayList<>();
        strList.add('a');
        strList.add('b');
        //  strList.add(100);
        
        //获取ArrayList的Class对象,反向的调用add()方法,添加数据
        //得到 strList 对象的字节码 对象
        Class listClass = strList.getClass(); 
        //获取add()方法
        Method m = listClass.getMethod("add", Object.class);
        //调用add()方法
        m.invoke(strList, 100.0f);
        
        //遍历集合
        for(Object obj : strList){
            System.out.println(obj);
        }
    }

多线程线程

死锁

线程 A 通过 synchronized (resource1) 获得 resource1 的监视器锁,然后通过Thread.sleep(1000);让线程 A 休眠 1s 为的是让线程 B 得到执行然后获取到 resource2 的监视器锁。线程 A 和线程 B 休眠结束了都开始企图请求获取对方的资源,然后这两个线程就会陷入互相等待的状态,这也就产生了死锁。上面的例子符合产生死锁的四个必要条件。

学过操作系统的朋友都知道产生死锁必须具备以下四个条件:

  1. 互斥条件:该资源任意一个时刻只由一个线程占用。
  2. 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
  3. 不剥夺条件:线程已获得的资源在末使用完之前不能被其他线程强行剥夺,只有自己使用完毕后才释放资源。
  4. 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

15.2 如何避免线程死锁?

我们只要破坏产生死锁的四个条件中的其中一个就可以了。

破坏互斥条件

这个条件我们没有办法破坏,因为我们用锁本来就是想让他们互斥的(临界资源需要互斥访问)。

破坏请求与保持条件

一次性申请所有的资源。

破坏不剥夺条件

占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它占有的资源。

破坏循环等待条件

靠按序申请资源来预防。按某一顺序申请资源,释放资源则反序释放。破坏循环等待条件。

作者:Guide哥
链接:https://juejin.im/post/5e18879e6fb9a02fc63602e2
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

说说 sleep() 方法和 wait() 方法区别和共同点?

两者最主要的区别在于:sleep 方法没有释放锁,而 wait 方法释放了锁

两者都可以暂停线程的执行。

职责

  • Wait:通常被用于线程间交互/通信

    ​ wait() 方法被调用后,线程不会自动苏醒,需要别的线程调用同一个对象上的 notify() 或者 notifyAll() 方法。

  • sleep:通常被用于暂停执行。

    ​ sleep() 方法执行完成后,线程会自动苏醒。或者可以使用 wait(long timeout)超时后线程会自动苏醒。

为什么我们调用 start() 方法时会执行 run() 方法,为什么我们不能直接调用 run() 方法?

这是另一个非常经典的 java 多线程面试问题,而且在面试中会经常被问到。很简单,但是很多人都会答不上来!

new 一个 Thread,线程进入了新建状态;调用 start() 方法,会启动一个线程并使线程进入了就绪状态,当分配到时间片后就可以开始运行了。 start() 会执行线程的相应准备工作,然后自动执行 run() 方法的内容,这是真正的多线程工作。 而直接执行 run() 方法,会把 run 方法当成一个 main 线程下的普通方法去执行,并不会在某个线程中执行它,所以这并不是多线程工作。

总结: 调用 start 方法方可启动线程并使线程进入就绪状态,而 run 方法只是 thread 的一个普通方法调用,还是在主线程里执行。

线程的状态


IO

Java 中 IO 流分为几种?

  • 按照流的流向分,可以分为输入流和输出流;
  • 按照操作单元划分,可以划分为字节流和字符流;
  • 按照流的角色划分为节点流和处理流。

Java Io 流共涉及 40 多个类,这些类看上去很杂乱,但实际上很有规则,而且彼此之间存在非常紧密的联系, Java I0 流的 40 多个类都是从如下 4 个抽象类基类中派生出来的。

  • InputStream/Reader: 所有的输入流的基类,前者是字节输入流,后者是字符输入流。
  • OutputStream/Writer: 所有输出流的基类,前者是字节输出流,后者是字符输出流。

按操作方式分类结构图:

Java底层知识:反射、IO_第1张图片
IO-操作方式分类.png

按操作对象分类结构图:

Java底层知识:反射、IO_第2张图片
IO-操作对象分类.png

既然有了字节流,为什么还要有字符流?

问题本质想问:不管是文件读写还是网络发送接收,信息的最小存储单元都是字节,那为什么 I/O 流操作要分为字节流操作和字符流操作呢?

回答:字符流是由 Java 虚拟机将字节转换得到的,问题就出在这个过程还算是非常耗时,并且,如果我们不知道编码类型就很容易出现乱码问题。所以, I/O 流就干脆提供了一个直接操作字符的接口,方便我们平时对字符进行流操作。如果音频文件、图片等媒体文件用字节流比较好,如果涉及到字符的话使用字符流比较好。

你可能感兴趣的:(Java底层知识:反射、IO)