Android复习系列①之《Java基础》

一、Java基础

1、java基本数据类型和引用类型

1.基本数据类型:

整型:byte,short,int,long
浮点型:float,double
字符型:char
布尔型:boolean

其中:

  • byte占一个字节
  • short、char占两个字节
  • int、float占四个字节
  • double、long占8个字节
  • boolean只有true和false。

2.引用数据类型:
类、 接口、 数组、 枚举、 注解

例如,String类型就是引用类型,还有Double,Byte,Long,Float,Char,Boolean,Short(注意这里和基本类型相比首字母是大写),简单来说,所有的非基本数据类型都是引用数据类型。

3.基本数据类型和引用数据类型区别
3.1 存储位置

  • 基本数据类型:在方法中定义的非全局基本数据类型变量的具体内容是存储在栈中的

  • 引用数据类型:引用数据类型变量,其具体内容都是存放在堆中的,而栈中存放的是其具体内容所在内存的地址

    public class Main{
       public static void main(String[] args){
           //基本数据类型
           int i=1;
           double d=1.2;
           //引用数据类型
           String str="helloworld";
       }
    }
    

在这里插入图片描述
3.2 传递方式

  • 基本数据类型:在方法中定义的非全局基本数据类型变量,调用方法时作为参数是按数值传递的
  • 引用数据类型:调用方法时作为参数是按引用传递的

4.自动装箱,自动拆箱

2、equals 和 ==

equals和==的区别

  • ==:基本类型比较值,引用类型比较对象的地址。

  • equals:是object自带的对比方法,基本类型无法使用,只能用于对象。默认也是用的==,比较两个对象的地址值。但是equals可以重写,例如String类就已经改写了equals方法,只要字符串内容相同,就返回true。

    // Object 的 equals方法
    public boolean equals(Object obj) {
        return (this == obj);
    }
    
    // String 的 equals方法
    public boolean equals(Object anObject) {
    if (this == anObject) {
        return true;
    }
    if (anObject instanceof String) {
        String anotherString = (String)anObject;
        int n = value.length;
        if (n == anotherString.value.length) {
            char v1[] = value;
            char v2[] = anotherString.value;
            int i = 0;
            while (n-- != 0) {
                if (v1[i] != v2[i])
                    return false;
                i++;
            }
            return true;
        }
    }
    return false;
    

}

```

面试题:

int a =1 ;
int b =1 ;
System.out.println(a == b);

String s1 ="123";
String s2 ="123";
System.out.println(s1 == s2);

String s3 = new String("123");
System.out.println(s2 == s3);

答案是:true,true,false
解析:对于基本数据类型来说,他们是作为常量在方法区中的常量池里面以HashSet策略存储起来的,对于这样的字符串 “123” 也是相同的道理,在常量池中,一个常量只会对应一个地址,因此不管是再多的"123" 都只会存储一个地址,所以所有他们的引用都是指向的同一块地址,因此基本数据类型和String常量是可以直接通过==来直接比较的。

面试题2:

Integer a =127 ;
Integer b =127 ;
System.out.println(a == b);

Integer m =128 ;
Integer n =128 ;
System.out.println(a == b);

答案:true,false
解析:对于基本数据的包装类型(Byte, Short, Character, Integer, Float, Double,Long, Boolean)除了Float和Double之外,其他的六种都是实现了常量池的,因此对于这些数据类型而言,一般我们也可以直接通过==来判断是否相等。
因为Integer在常量池中的存储范围为[-128,127],127在这范围内,因此是直接存储于常量池的,而128不在这范围内,所以会在堆内存中创建一个新的对象来保存这个值,所以m,n分别指向了两个不同的对象地址,故而导致了不相等。

equals和hashcode的关系?
默认情况下,equals相等,hashcode必相等,hashcode相等,equals不是必相等。hashcode基于内存地址计算得出,可能会相等,虽然几率微乎其微。

3、static关键字

在类中,用static声明的成员变量为静态成员变量,也成为类变量。static修饰的成员方法为静态方法。静态变量的生命周期和类相同。

这里要强调一下:

普通成员变量和成员方法,从属于对象

静态成员变量和静态方法,从属于类

3.1.static关键字的用途

一句话描述就是:方便在没有创建对象的情况下进行调用(方法/变量)。

被static关键字修饰的方法或者变量不需要依赖于对象来进行访问,只要类被加载了,就可以通过类名去进行访问。

3.2.static方法

由于静态方法不依赖于任何对象就可以直接访问,因此对于静态方法来说,是没有this的,因为不依附于任何对象,既然都没有对象,就谈不上this了,并且由于此特性,在静态方法中不能访问类的非静态成员变量和非静态方法,因为非静态成员变量和非静态方法都必须依赖于具体的对象才能被调用。反之,虽然在静态方法中不能访问非静态成员方法和非静态成员变量,但是在非静态成员方法中是可以访问静态成员方法和静态成员变量。

特别说明:static方法是属于类的,非实例对象,在JVM加载类时,就已经存在内存中,不会被虚拟机GC回收掉,这样内存负荷会很大,但是非static方法会在运行完毕后被虚拟机GC掉,减轻内存压力。

3.3.static变量

静态变量被所有对象共享,在内存中只有一个副本,在类初次加载的时候才会初始化。

非静态变量是对象所拥有的,在创建对象的时候被初始化,存在多个副本,各个对象拥有的副本互不影响。

3.4.静态变量和成员变量的区别

(1)所属不同:静态变量属于类,为类变量;成员变量属于对象,称为对象变量
(2)内存中位置不同:静态变量位于方法区中的静态区,成员变量存储于堆内存
(3)生命周期不同:成员变量随着实例对象创建而存在,随着实例对象被回收而消失。静态变量随着类的加载而存在,随着类的消失而消失。
(4)调用不同:静态变量可以通过类名调用,也可以通过对象名调用,成员变量只能通过对象名调用

所以,成员变量可以称为对象的特有数据,静态变量称为对象的共享数据。

3.5. static代码块

     static{
        System.out.println("静态代码块");  
	}      

执行时机:静态代码块在类被加载的时候就运行了,而且只运行一次,并且优先于各种代码块以及构造函数。如果一个类中有多个静态代码块,就会按照书写的顺序执行。

静态代码块的作用:一般情况下,如果有些代码需要在项目启动的时候执行,这时就需要静态代码快,比如一个项目启动需要加载很多配置文件等资源,就可以都放在静态代码块中。

执行顺序:.静态代码块>构造代码块>构造函数

4、final关键字

可以声明成员变量、方法、类以及本地变量
final 成员变量必须在声明的时候初始化或者在构造器中初始化,否则就会报编译错误
final 变量是只读的
final 申明的方法不可以被子类的方法重写
final 类通常功能是完整的,不能被继承
final 变量可以安全的在多线程环境下进行共享,而不需要额外的同步开销
final 关键字提高了性能,JVM 和 Java 应用都会缓存 final 变量,会对方法、变量及类进行优化
方法的内部类访问方法中的局部变量,必须用 final 修饰才能访问

5、String、StringBuffer、StringBuilder

String 为什么要设计成不可变的?

String是不可变的(修改String时,不会在原有的内存地址修改,而是重新指向一个新对象),String用final修饰,不可继承,String本质上是个final的char[]数组,所以char[]数组的内存地址不会被修改,而且String 也没有对外暴露修改char[]数组的方法。不可变性可以保证线程安全以及字符串串常量池的实现。

三者在执行速度方面的比较:StringBuilder > StringBuffer > String

String每次变化一个值就会开辟一个新的内存空间

StringBuilder:线程非安全的

StringBuffer:线程安全的

对于三者使用的总结:

1.如果要操作少量的数据用 String。

2.单线程操作字符串缓冲区下操作大量数据用 StringBuilder。

3.多线程操作字符串缓冲区下操作大量数据用 StringBuffer。

String 是 Java 语言非常基础和重要的类,提供了构造和管理字符串的各种基本逻辑。它是典型的 Immutable 类,被声明成为 final class,所有属性也都是 final 的。也由于它的不可变性,类似拼接、裁剪字符串等动作,都会产生新的 String 对象。由于字符串操作的普遍性,所以相关操作的效率往往对应用性能有明显影响。

StringBuffer 是为解决上面提到拼接产生太多中间对象的问题而提供的一个类,我们可以用 append 或者 add 方法,把字符串添加到已有序列的末尾或者指定位置。StringBuffer 本质是一个线程安全的可修改字符序列,它保证了线程安全,也随之带来了额外的性能开销,所以除非有线程安全的需要,不然还是推荐使用它的后继者,也就是 StringBuilder。

StringBuilder 是 Java 1.5 中新增的,在能力上和 StringBuffer 没有本质区别,但是它去掉了线程安全的部分,有效减小了开销,是绝大部分情况下进行字符串拼接的首选。

6、异常处理

Java异常结构中定义有Throwable类。 Exception和Error为其子类。

Exception是程序本身可以处理的异常,这种异常分两大类:运行时异常非运行时异常,程序中应当尽可能去处理这些异常。

运行时异常:都是RuntimeException类及其子类异常,如NullPointerException、IndexOutOfBoundsException等, 这些异常是不检查异常,程序中可以选择捕获处理,也可以不处理。这些异常一般是由程序逻辑错误引起的, 程序应该从逻辑角度尽可能避免这类异常的发生。

非运行时异常:是需要显示用try-catch捕捉处理的异常,如IOException等

Error是程序无法处理的错误,比如OutOfMemoryError、StackOverflowError。这些异常发生时, Java虚拟机(JVM)一般会选择线程终止。

Java语言异常处理

  1. 对代码块用try…catch进行异常捕获处理;
	finally块没有处理异常的能力。处理异常的只能是catch块。
	
	不管有没有异常,finally 中的代码都会执行
	
	当 trycatch 中有 return 时,finally 中的代码依然会继续执行
  1. 在方法体外用throws进行抛出声明
	public void foo() throws ExceptionType1 , ExceptionType2 ,ExceptionTypeN
	{ 
	     //foo内部可以抛出 ExceptionType1 , ExceptionType2 ,ExceptionTypeN 类的异常,或者他们的子类的异常对象。
	}

3.在代码块用throw手动抛出一个异常对象

public void save(User user)
	{
	      if(user  == null) 
	          throw new IllegalArgumentException("User对象为空");
	      //......
	}

异常处理的两个基本原则:

  1. 尽量不要捕获类似 Exception 这样的通用异常,而是应该捕获特定异常。
  2. 不要生吞异常。

7、谈谈对java多态的理解?(重要)

什么是多态

多态是同一个行为具有多个不同表现形式或形态的能力。

多态就是同一个接口,使用不同的实例而执行不同操作

如图所示:

Android复习系列①之《Java基础》_第1张图片

多态的必要条件

1.子类继承父类并重写父类的方法。
2.父类的引用指向子类对象。

定义格式:父类类型 变量名=new 子类类型();

多态中成员的特点

1. 多态成员变量:编译运行看左边 
   Fu f=new Zi();
 	System.out.println(f.num);//f是Fu中的值,只能取到父中的值
2. 多态成员方法:编译看左边,运行看右边
	Fu f1=new Zi();
 	System.out.println(f1.show());//f1的门面类型是Fu,但实际类型是Zi, 所以调用的是重写后的方法。 

多态的好处:

1.可替换性:多态对已存在代码具有可替换性。例如,多态对圆Circle类工作,对其他任何圆形几何体,如圆环,也同样工作。

2.可扩充性:多态对代码具有可扩充性。增加新的子类不影响已存在类的多态性、继承性,以及其他特性的运行和操作。实际上新加子类更容易获得多态功能。

3.接口性:多态是超类通过方法签名,向子类提供了一个共同接口,由子类来完善或者覆盖它而实现的。

4.灵活性:它在应用中体现了灵活多样的操作,提高了使用效率。

5.简化性:多态简化对应用软件的代码编写和修改过程,尤其在处理大量对象的运算和操作时,这个特点尤为突出和重要。

多态的例子

public class Test {
    public static void main(String[] args) {
      show(new Cat());  // 以 Cat 对象调用 show 方法
      show(new Dog());  // 以 Dog 对象调用 show 方法
                
      Animal a = new Cat();  // 向上转型  
      a.eat();               // 调用的是 Cat 的 eat
      Cat c = (Cat)a;        // 向下转型  
      c.work();        // 调用的是 Cat 的 work
  }  
            
    public static void show(Animal a)  {
      a.eat();  
        // 类型判断
        if (a instanceof Cat)  {  // 猫做的事情 
            Cat c = (Cat)a;  
            c.work();  
        } else if (a instanceof Dog) { // 狗做的事情 
            Dog c = (Dog)a;  
            c.work();  
        }  
    }  
}
 
abstract class Animal {  
    abstract void eat();  
}  
  
class Cat extends Animal {  
    public void eat() {  
        System.out.println("吃鱼");  
    }  
    public void work() {  
        System.out.println("抓老鼠");  
    }  
}  
  
class Dog extends Animal {  
    public void eat() {  
        System.out.println("吃骨头");  
    }  
    public void work() {  
        System.out.println("看家");  
    }  
}

多态的实现方式:

基本上是重载、接口、继承(重写) 可通过这三种方式实现多态

8、抽象和接口(重要)

抽象类的意义?

为其子类提供一个公共的类型,封装子类中的重复内容,定义抽象方法,子类虽然有不同的实现,但是定义是一致的。

接口的意义?

规范、扩展、回调。

共同点

  1. 是上层的抽象层。
  2. 都不能被实例化。
  3. 都能包含抽象的方法,这些抽象的方法用于描述类具备的功能,但是不提供具体的实现。

区别

  1. 在抽象类中可以写非抽象的方法,从而避免在子类中重复书写他们,这样可以提高代码的复用性,这是抽象类的优势,接口中只能有抽象的方法。
  2. 多继承:一个类只能继承一个直接父类,这个父类可以是具体的类也可是抽象类,但是一个类可以实现多个接口。
  3. 抽象类可以有默认的方法实现,接口根本不存在方法的实现。
  4. 子类使用extends关键字来继承抽象类。如果子类不是抽象类的话,它需要提供抽象类中所有声明方法的实现。子类使用关键字implements来实现接口。它需要提供接口中所有声明方法的实现。
  5. 构造器:抽象类可以有构造器,接口不能有构造器。
  6. 和普通Java类的区别:除了你不能实例化抽象类之外,抽象类和普通Java类没有任何区别,接口是完全不同的类型。
  7. 访问修饰符:抽象方法可以有public、protected和default修饰符,接口方法默认是public abstract**** 。你不可以使用其它修饰符。接口中的所有属性默认为:public static final ****.
  8. main方法:抽象方法可以有main方法并且我们可以运行它,接口没有main方法,因此我们不能运行它。
  9. 速度:抽象类比接口速度要快,接口是稍微有点慢的,因为它需要时间去寻找在类中实现的方法。
  10. 添加新方法:如果你往抽象类中添加新的方法,你可以给它提供默认的实现。因此你不需要改变你现在的代码。如果你往接口中添加方法,那么你必须改变实现该接口的类。

8、内部类,匿名内部类

内部类
非静态内部类没法在外部类的静态方法中实例化。
非静态内部类的方法可以直接访问外部类的所有数据,包括私有的数据。
在静态内部类中调用外部类成员,成员也要求用 static 修饰。
创建静态内部类的对象可以直接通过外部类调用静态内部类的构造器;创建非静态的内部类的对象必须先创建外部类的对象,通过外部类的对象调用内部类的构造器。
匿名内部类
匿名内部类不能定义任何静态成员、方法
匿名内部类中的方法不能是抽象的
匿名内部类必须实现接口或抽象父类的所有抽象方法
匿名内部类不能定义构造器
匿名内部类访问的外部类成员变量或成员方法必须用 final 修饰

9、集合框架

所有的集合都在 java.util 包下,java的集合几乎是从Collection 和 map这两个接口中派生出来的,而这两个接口又有一些子类(包括子接口和实现类)

9.1.集合和数组的区别:

数组和链表:https://zhuanlan.zhihu.com/p/363343364
在这里插入图片描述

9.2. 常用集合的分类:

标题

9.3. list和set的区别:

在这里插入图片描述
List:
在这里插入图片描述

(1)ArrayList: 底层数据结构是数组,查询快,增删慢,线程不安全,效率高,可以存储重复元素
(2)LinkedList:底层数据结构是链表,查询慢,增删快,线程不安全,效率高,可以存储重复元素
(3)Vector:底层数据结构是数组,查询快,增删慢,线程安全,效率低,可以存储重复元素

Set(Set 接口存储一组唯一无序的对象。)
HashSet
HashSet 是一个没有重复元素的集合。它是由HashMap实现的,不保证元素的顺序(这里所说的没有顺序是指:元素插入的顺序与输出的顺序不一致),而且HashSet允许使用null。但是只允许有一个null元素!

LinkedHashSet
LinkedHashSet继承自HashSet,其底层是基于LinkedHashMap来实现的,有序,非同步。(LinkedHashSet集合同样是根据元素的hashCode值来决定元素的存储位置,但是它同时使用链表维护元素的次序。这样使得元素看起来像是以插入顺序保存的,也就是说,当遍历该集合时候,LinkedHashSet将会以元素的添加顺序访问集合的元素。)

TreeSet
TreeSet是一个有序集合,其底层是基于TreeMap实现的,非线程安全。TreeSet可以确保集合元素处于排序状态。

Set和List的区别
(1) Set 接口实例存储的是无序的,不重复的数据。List 接口实例存储的是有序的,可以重复的元素。

(2)Set检索效率低下,删除和插入效率高,插入和删除不会引起元素位置改变 <实现类有HashSet,TreeSet>。

(3)List和数组类似,可以动态增长,根据实际存储的数据的长度自动增长List的长度。查找元素效率高,插入删除效率低,因为会引起其他元素位置改变 <实现类有ArrayList,LinkedList,Vector> 。

9.5. Map

Map用于保存具有映射关系的数据,Map里保存着两组数据:key和value,它们都可以使任何引用类型的数据,但key不能重复。所以通过指定的key就可以取出对应的value。

HashMap和HashTable的比较:
在这里插入图片描述
TreeMap
在这里插入图片描述
HashMap源码解读(重点)

HashMap基础
HashMap继承了AbstractMap类,实现了Map,Cloneable,Serializable接口
数据结构(jdk1.7:数组+链表,1.8后增加了红黑树:数组+链表+红黑树):
Android复习系列①之《Java基础》_第2张图片

HashMap的容量,默认是16
HashMap的加载因子,默认是0.75
当HashMap中元素数超过容量 x 加载因子时,HashMap会进行扩容。

HashMap类中的元素是Node类,翻译过来就是节点,是定义在HashMap中的一个内部类,实现了Map.Entry接口。

Node类的基本属性有:

hash:key的哈希值 
key:节点的key,类型和定义HashMap时的key相同 
value:节点的value,类型和定义HashMap时的value相同 
next:该节点的下一节点

next记录的是下一个节点本身,也是一个Node节点,这个Node节点也有next属性,记录了下一个节点,于是,只要不断的调用Node.next.next.next……,就可以得到每个节点

HashMap存值过程

  1. 通过键的Hash值确定数组的位置。
  2. 找到以后,如果该位置无节点,直接存放。
  3. 该位置有节点,则遍历该节点以及后续的节点,比较key值,相等则覆盖,没有就新增节点,默认使用链表,相连节点数超过8的时候,在jdk 1.8中会变成红黑树。
  4. 如果Hashmap中的数组使用情况超过一定比例,就会扩容,默认扩容两倍。

常问问题:

  1. hashMap如何解决hash碰撞?有其它方式吗?
    HashMap使用链表来解决碰撞问题,当碰撞发生了,对象将会存储在链表的下一个节点中。即所谓的拉链法
    其它方式:如二次哈希

  2. hashMap怎么在链表上添加数据?在链表的前面还是后面?
    jdk1.7头插法
    jdk1.8的链表从后面插入

  3. Hashmap的默认容量?为什么是16不是15?输入17会是什么容量?
    16,因为只能是2的指数幂容量
    输入17容量是32。(如果HashMap初始化的时候指定了容量,HashMap会把这个容量修改为2的倍数,然后创建对应长度的数组)

  4. HashMap的容量为什么必须要2的指数幂容量?
    计算下标的算法很简单,hash值 和 (length-1)按位与,使用length-1的意义在于,length是2的倍数,所以length-1在二进制来说每位都是1,这样可以保证最大的程度的散列hash值,否则,当有一位是0时,不管hash值对应位是1还是0,按位与后的结果都是0,会造成散列结果的重复。
    总结来说就是:减少hash碰撞。
    备注:与运算,同为1才为1

  5. HashMap的数组是什么时候创建的?
    第一次put的时候
    原因:懒加载的思想,节省内存
    如果new一个hashMap 但并没有使用(或并没有立即使用) 会浪费内存空间

  6. hashMap扩容的原理
    当HashMap中元素数超过容量 x 加载因子时,HashMap会进行扩容。
    当HashMap决定扩容时,会调用HashMap类中的resize(int newCapacity)方法,参数是新的table长度。在JDK1.7和JDK1.8的扩容机制有很大不同。

  7. 传统jdk1.7hashMap的缺点?
    a).耗时,链表的轮训非常耗时 ---->时间复杂度 0(n)
    b).并发场景,hashMap是线程不安全的

  8. Jdk1.8为什么引入红黑树?
    因为Map中桶的元素初始化是链表保存的,其查找性能是O(n),而树结构能将查找性能提升到O(log(n))。当链表长度很小的时候,即使遍历,速度也非常快,但是当链表长度不断变长,对查询性能有一定的影响,所以才需要转成树。

  9. hashTable和hashMap的区别
    hashTable是为了解决并发(线程安全)的
    解决方式:给整个HashTable加锁 synchronized
    HashTable:底层数组+链表实现,无论key还是value都不能为null,线程安全,实现线程安全的方式是在修改数据时锁住整个HashTable(它把所有方法都加上synchronized关键字来实现线程安全),效率低,
    由此延伸出ConcurrentHashMap 它做了相关优化

  10. ConcurrentHashmap和HashTable的区别
    ConcurrentHashMap不需要锁整个Map,相反它划分了N段(segments),要操作哪一段才上锁哪段数据。它给链表或者红黑树所在的节点加锁,不光线程安全,速度相比hashTable的效率也有了N倍提升,默认提升16倍(读操作不加锁,由于ConcurrentHashMap中HashEntry的value变量是 volatile的,也能保证读取到最新的值,对应下面一个问题)

  11. ConcurrentHashMap get方法没有加锁实现,为什么不会导致脏数据?
    给变量加Volatile 保证数据可见性

  12. 1.8的hashMap数据结构,什么时候会转成红黑树
    数组+链表+红黑树(链表长度超过8,并且数组长度不小于64)
    当链表的值小于6则会从红黑树转回链表

  13. 扩容后存储位置的计算方式
    jdk1.7通过再次indexFor()找到数组位置
    jdk1.8通过高低位的桶直接在链表尾部添加

扩展:二叉树 红黑树 可以看数据结构部分

10、IO

IO流的分类:

按照“流”的数据流向,可以将其化分为:输入流输出流

按照“流”中处理数据的单位,可以将其区分为:字节流字符流。在java中,字节是占1个Byte,即8位;而字符是占2个Byte,即16位。而且,需要注意的是,java的字节是有符号类型,而字符是无符号类型!

字节流的抽象基类:
InputStream,OutputStream

字符流的抽象基类:
Reader,Writer

由这四个类派生出来的子类名称都是以其父类名作为子类名的后缀,如InputStream的子类FileInputStream,Reader的子类FileReader。

字节流和字符流的区别

字节流操作的基本单元是字节;字符流是Unicode字符
字节流不使用缓冲区,字符流使用缓冲区
字节流通常用于处理二进制数据,实际上它可以处理任意类型的数据,但它不支持直接写入或读取Unicode码元;字符流通常处理文本数据,它支持写入及读取Unicode码元。

IO和NIO(new IO)区别
IO面向流,NIO面向缓冲区
IO是阻塞的,NIO是非阻塞的
Java NIO的选择器允许一个单独的线程来监视多个输入通道,你可以注册多个通道使用一个选择器,然后使用一个单独的线程来“选择”通道:这些通道里已经有可以处理的输入,或者选择已准备写入的通道。这种选择机制,使得一个单独的线程很容易来管理多个通道

11、反射(非常重要)

反射是框架设计的灵魂

11.1.思考:Java中创建对象大概有几种方式?

  1. 使用new关键字:这是我们最常见的也是最简单的创建对象的方式
  2. 使用Clone的方法:无论何时我们调用一个对象的clone方法,JVM就会创建一个新的对象,将前面的对象的内容全部拷贝进去
  3. 使用反序列化:当我们序列化和反序列化一个对象,JVM会给我们创建一个单独的对象
  4. 反射

11.2.什么是反射?反射能干什么?反射的优缺点?

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

如果给定一个类名,就可以通过反射机制来获取类的所有信息,也可以动态的创建对象和编译。一般来说反射是用来做框架的,或者说可以做一些抽象度比较高的底层代码,常用的需求场景有:动态代理、工厂模式优化、Java JDBC数据库操作等。有一句很经典的话:反射是框架设计的灵魂

优点
灵活性高。因为反射属于动态编译,即只有到运行时才动态创建 、获取对象实例。

编译方式说明:

  1. 静态编译:在编译时确定类型、绑定对象。如常见的使用new关键字创建对象
  2. 动态编译:运行时确定类型、绑定对象。动态编译体现了Java的灵活性、多态特性、降低类之间的藕合性

缺点
执行效率低。 因为反射的操作主要通过JVM执行,所以时间成本 高于直接执行相同操作。

因为接口的通用性,Java的invoke方法是传object和object[]数组的。基本类型参数需要装箱和拆箱,产生大量额外的对象和内存开销,频繁促发GC。
编译器难以对动态调用的代码提前做优化,比如方法内联。
反射需要按名检索类和方法,有一定的时间开销。

11.3.反射的使用

在使用Java反射机制时,主要步骤包括:

  1. 获取 目标类型的Class对象
  2. 通过 Class 对象分别获取Constructor类对象、Method类对象 、 Field 类对象
  3. 通过 Constructor类对象、Method类对象 、Field类对象分别获取类的构造函数、方法、属性的具体信息,并进行后续操作
    //步骤1:
    //获取目标类型的class对象方式1:static method Class.forName 前提:已明确类的全路径名(最常用)
    Class cls = Class.forName("com.text.Person");
    //获取目标类型的class对象方式2:Object.getClass()  适合有对象示例的情况下
    Person person= new Person(); 
    Class<?> cls = person.getClass(); 
    //获取目标类型的class对象方式3:T.class语法   说明:仅适合在编译前就已经明确要操作的 Class
    Class<?> classType = Person.class; 
    
    //步骤2:
	// 以下方法都属于Class类的方法。
	<-- 1. 获取类的构造函数(传入构造函数的参数类型)->>
	// a. 获取指定的构造函数 (公共 / 继承)
	Constructor<T> getConstructor(Class<?>... parameterTypes)
	// b. 获取所有的构造函数(公共 / 继承) 
	Constructor<?>[] getConstructors(); 
	// c. 获取指定的构造函数 ( 不包括继承)
	Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes) 
	// d. 获取所有的构造函数( 不包括继承)
	Constructor<?>[] getDeclaredConstructors(); 
	// 最终都是获得一个Constructor类对象
	 
	// 特别注意:
	// 1. 不带 "Declared"的方法支持取出包括继承、公有(Public)、不包括有(Private)的构造函数
	// 2. 带 "Declared"的方法是支持取出包括公共(Public)、保护(Protected)、默认(包)访问和私有(Private)的构造方法,但不包括继承的构造函数
	// 下面同理
	 
	<--  2. 获取类的属性(传入属性名) -->
	// a. 获取指定的属性(公共 / 继承)
	Field getField(String name) ;
	// b. 获取所有的属性(公共 / 继承)
	Field[] getFields() ;
	// c. 获取指定的所有属性 (不包括继承)
	Field getDeclaredField(String name)// d. 获取所有的所有属性 (不包括继承)
	Field[] getDeclaredFields()// 最终都是获得一个Field类对象
	 
	<-- 3. 获取类的方法(传入方法名 & 参数类型)-->
	// a. 获取指定的方法(公共 / 继承)
	Method getMethod(String name, Class<?>... parameterTypes)// b. 获取所有的方法(公共 / 继承)
	Method[] getMethods()// c. 获取指定的方法 ( 不包括继承)
	Method getDeclaredMethod(String name, Class<?>... parameterTypes)// d. 获取所有的方法( 不包括继承)
	Method[] getDeclaredMethods()// 最终都是获得一个Method类对象
	 
	<-- 4. Class类的其他常用方法 -->
	getSuperclass(); 
	// 返回父类
	String getName(); 
	// 作用:返回完整的类名(含包名,如java.lang.String ) 
	Object newInstance(); 
	// 作用:快速地创建一个类的实例
	// 具体过程:调用默认构造器(若该类无默认构造器,则抛出异常 
	// 注:若需要为构造器提供参数需使用java.lang.reflect.Constructor中的newInstance()

	步骤3:通过 Constructor类对象、Method类对象、Field类对象分别获取类的构造函数、方法、属性的具体信息进行操作
	// 以下方法都分别属于`Constructor`类、Method类、Field类的方法。
	
	<-- 1. 通过Constructor 类对象获取类构造函数信息 -->
	String getName()// 获取构造器名
	Class getDeclaringClass()// 获取一个用于描述类中定义的构造器的Class对象
	int getModifiers()// 返回整型数值,用不同的位开关描述访问修饰符的使用状况
	Class[] getExceptionTypes()// 获取描述方法抛出的异常类型的Class对象数组
	Class[] getParameterTypes()// 获取一个用于描述参数类型的Class对象数组
	 
	<-- 2. 通过Field类对象获取类属性信息 -->
	String getName()// 返回属性的名称
	Class getDeclaringClass()// 获取属性类型的Class类型对象
	Class getType()// 获取属性类型的Class类型对象
	int getModifiers()// 返回整型数值,用不同的位开关描述访问修饰符的使用状况
	Object get(Object obj)// 返回指定对象上 此属性的值
	void set(Object obj, Object value) // 设置 指定对象上此属性的值为value
	 
	<-- 3. 通过Method 类对象获取类方法信息 -->
	String getName()// 获取方法名
	Class getDeclaringClass()// 获取方法的Class对象 
	int getModifiers()// 返回整型数值,用不同的位开关描述访问修饰符的使用状况
	Class[] getExceptionTypes()// 获取用于描述方法抛出的异常类型的Class对象数组
	Class[] getParameterTypes()// 获取一个用于描述参数类型的Class对象数组
	
	使用方法:method.invoke(Object obj,Object... args)
	
	<--额外:java.lang.reflect.Modifier-->
	// 作用:获取访问修饰符
	 
	static String toString(int modifiers)   
	// 获取对应modifiers位设置的修饰符的字符串表示
	 
	static boolean isXXX(int modifiers) 
	// 检测方法名中对应的修饰符在modifiers中的值

12、引用类型

  1. 强引用(FinalReference):在内存不足时不会被回收。平常用的最多的对象,如新创建的对象。
  2. 软引用(SoftReference):在内存足够的时候,软引用对象不会被回收,只有在内存不足时,系统则会回收软引用对象,如果回收了软引用对象之后仍然没有足够的内存,才会抛出内存溢出异常。
  3. 弱引用(WeakReferenc):无论内存是否足够,只要 JVM 开始进行垃圾回收,那些被弱引用关联的对象都会被回收。
  4. 虚引用(PhantomReference):虚引用是最弱的一种引用关系,如果一个对象仅持有虚引用,那么它就和没有任何引用一样,它随时可能会被回收.

13、java泛型

1. 为什么要引入泛型?

在Java SE 1.5之前,没有泛型的情况的下,通过对类型Object的引用来实现参数的“任意化”,“任意化”带来的缺点是要做显式的强制类型转换,而这种转换是要求开发者实际参数类型可以预知的情况下进行的。对于强制类型换错误的情况,编译器可能不提示错误,在运行的时候出现异常,这是一个安全隐患。

2. 说一下对泛型的理解

泛型的本质是参数化类型,在不创建新的类型的情况下,通过泛型指定不同的类型来控制形参具体限制的类型。也就是说在泛型的使用中,操作的数据类型被指定为一个参数,这种参数可以被用在类、接口和方法中,分别被称为泛型类、泛型接口和泛型方法。 Java语言引入泛型的好处是安全简单.
泛型是Java中的一种语法糖,能够在代码编写的时候起到类型检测的作用,但是虚拟机是不支持这些语法的。

泛型的特性:泛型只在编译阶段有效

3.泛型的使用:泛型类、泛型方法、泛型接口

泛型类:是在实例化类的时候指明泛型的具体类型;

//此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型
//在实例化泛型类时,必须指定T的具体类型
public class Generic<T>{ 
    //key这个成员变量的类型为T,T的类型由外部指定  
    private T key;

    public Generic(T key) { //泛型构造方法形参key的类型也为T,T的类型由外部指定
        this.key = key;
    }

    public T getKey(){ //泛型方法getKey的返回值类型为T,T的类型由外部指定
        return key;
    }
}

泛型接口与泛型类的定义及使用基本相同

泛型方法:是在调用方法的时候指明泛型的具体类型 。

/**
 * 泛型方法的基本介绍
 * @param tClass 传入的泛型实参
 * @return T 返回值为T类型
 * 说明:
 *     1)public 与 返回值中间非常重要,可以理解为声明此方法为泛型方法。
 *     2)只有声明了的方法才是泛型方法,泛型类中的使用了泛型的成员方法并不是泛型方法。
 *     3)表明该方法将使用泛型类型T,此时才可以在方法中使用泛型类型T。
 *     4)与泛型类的定义一样,此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型。
 */
public <T> T genericMethod(Class<T> tClass)throws InstantiationException ,
  IllegalAccessException{
        T instance = tClass.newInstance();
        return instance;
}

4.泛型的参数到底是什么?

  1. 不是一个类,也不是一个接口。只是一个标记符号,一个代号
  2. 代什么?代表这个类型内部某个通用的类型

5.泛型的优点:

  1. 类型安全,避免类型的强转。
  2. 提高了代码的可读性,不必要等到运行的时候才去强制转换。

6.什么是泛型中的限定通配符和非限定通配符 ?

限定通配符对类型进行了限制。有两种限定通配符,一种是它通过确保类型必须是T的子类来设定类型的上界,另一种是它通过确保类型必须是T的父类来设定类型的下界。泛型类型必须用限定内的类型来进行初始化,否则会导致编译错误。

表示了非限定通配符,因为可以用任意类型来替代。

7.什么是类型擦除?

不管泛型的类型传入哪一种类型实参,对于Java来说,都会被当成同一类处理,在内存中也只占用一块空间。通俗一点来说,就是泛型只作用于代码编译阶段,在编译过程中,对于正确检验泛型结果后,会将泛型的信息擦除,也就是说,成功编译过后的class文件是不包含任何泛型信息的。

你可能感兴趣的:(#面试专栏,java,android,面试)