Java

java基础面试知识点

2018-04-03  本文已影响62人  Jenchar

注:都是在百度搜索整理的答案,如有侵权和错误,希告知更改。

一、java中==和equals和hashCode的区别

1)“==”运算符用来比较两个变量的值是否相等。也就是说,该运算符用于比较变量对应的内存中所存储的数值是否相同,要比较两个基本类型的数据或两个引用变量是否相等,只能使用“==”运算符。

具体而言,如果两个变量是基本数据类型,可以直接使用“==”运算符来比较其对应的值是否相等。如果一个变量指向的数据是对象(引用类型),那么,此时涉及了两块内存,对象本身占用了一块内存(堆内存),变量也占用一块内存,例如,对于赋值语句:

String s = new String();  

变量s占用一块存储空间,而new String()则存储在另外一块存储空间里,此时,变量s所对应内存中存储的数值就是对象占用的那块内存的首地址。对于指向对象类型的变量,如果要比较两个变量是否指向同一个对象,即要看这两个变量所对应的内存中的数值是否相等(这两个对象是否指向同一块存储空间),这时候就可以用“==”运算符进行比较。但是,如果要比较这两个对象的内容是否相等,那么用“==”运算符就无法实现了。

2)equals是Object类提供的方法之一。每一个Java类都集成自Object类,所以每一个对象都具有equals这个方法。Object类中定义的equals(Object)方法的情况下,equals(Object)与“==”运算符一样,比较的是引用。

相比“==”运算符,equals(Object)方法的特殊之处就在于它可以被覆盖,所以可以通过覆盖的方法让它不是比较引用而是比较数据内容,例如String类的equals方法是用于比较两个独立对象的内容是否相同,即堆中的内容是否相同,以下面的代码为例:

String s1 = new String("Hello");  
String s2 =new String("Hello");  

两条new语句创建了两个对象,然后用s1、s2这两个变量分别指向一个对象,这是两个不同的对象,它们的首地址是不同的,即s1和s2中存储的数值是不相同的,所以,表达式s1==s2将返回false,而这两个对象中的内容是相同的,所以,表达式s1.equals(s2)将返回true。

如果一个类没有自己定义equals()方法,那么它将继承Object类的equals()方法,Object类的equals()方法的实现代码如下:

boolean equals(Object object){  
        return this==object;  
}  

通过以上例子可以说明,如果一个类没有自己定义equals()方法,它默认的equals()方法(从Object类继承的)就是使用“==”运算符,也是在比较两个变量指向的对象是否是同一对象,此时使用equals()方法和使用“==”运算符会得到同样的结果。若比较的是两个独立的对象,则总返回false。如果编写的类希望能够比较该类创建的两个实例对象的内容是否相同,那么必须覆盖equals()方法,由开发人员自己编写代码来决定在什么情况下即可认为两个对象的内容是相同的。

3)hashCode()方法是从Object类中继承过来的,它也用来鉴定两个对象是否相等。Object类中的hashCode()方法返回对象在内存中地址转换成的一个int值,所以如果没有重写hashCode()方法,任何对象的hashCode()方法都是不相等的。

虽然equals()方法也是用来判断两个对象是否相等的,但是它与hashCode()方法是有区别的。一般来讲,equals()方法是给用户调用的,如果需要判断两个对象是否相等的,可以重写equals()方法,然后在代码中调用,这样就可以判断它们是否相等了。对于hashCode()方法,用户一般不会去调用它,例如在hashmap中,由于key是不可以重复的,它在判断key是否重复时就判断了hashCode()方法,而且也用到了equals()方法。此处“不可以重复”指的是equals()和hashCode()只要有一个不等就可以了。所以,hashCode()方法相当于是一个对象的编码,就好像文件中的md5,它与equals()方法的不同之处就在于它返回的是int型,比较起来不直观。

一般在覆盖equals()方法的同时也要覆盖hashCode()方法,否则,就会违反Object.hashCode的通用约定,从而导致该类无法与所有基于散列值(hash)集合类(HashMap、HashSet和Hashtable)结合在一起正常运行。

hashCode()方法的返回值和equals()方法的关系如下:
x.equals(y)返回true,即两个对象根据equals()方法比较是相等的,那么调用这两个对象中任意一个对象的hashCode()方法都必须产生同样的整数结果。如果x.equals(y)返回false,即两个对象根据equals()方法比较是不相等的,那么x和y的hashCode()方法的返回值有可能相等,也有可能不相等。反之,hashCode()方法的返回值不相等,一定能推出equals()方法的返回值也不相等,而hashCode()方法的返回值相等,equals()方法的返回值则可能相等,也可能不相等。

二、int、char、long各占多少字节数

Java基本类型占用的字节数:
1字节: byte , boolean
2字节: short , char
4字节: int , float
8字节: long , double
注:1字节(byte)=8位(bits)

三、int与integer的区别

  1. Integer是int的包装类;int则是java的一种基本数据类型
  2. Integer变量必须实例化后才能使用;而int变量不需要
  3. Integer实际是对象的引用,当new一个Integer时,实际上是生成一个指针指向此对象;而int则是直接存储数据值
  4. Integer的默认值是null;int的默认值是0

四、谈谈对java多态的理解

所谓多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量倒底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。因为在程序运行时才确定具体的类,这样,不用修改源程序代码,就可以让引用变量绑定到各种不同的类实现上,从而导致该引用调用的具体方法随之改变,即不修改程序代码就可以改变程序运行时所绑定的具体代码,让程序可以选择多个运行状态,这就是多态性。
比如你是一个酒神,对酒情有独钟。某日回家发现桌上有几个杯子里面都装了白酒,从外面看我们是不可能知道这是些什么酒,只有喝了之后才能够猜出来是何种酒。你一喝,这是剑南春、再喝这是五粮液、再喝这是酒鬼酒….在这里我们可以描述成如下:

酒 a = 剑南春
酒 b = 五粮液
酒 c = 酒鬼酒

 这里所表现的的就是多态。剑南春、五粮液、酒鬼酒都是酒的子类,我们只是通过酒这一个父类就能够引用不同的子类,这就是多态——我们只有在运行的时候才会知道引用变量所指向的具体实例对象。

 诚然,要理解多态我们就必须要明白什么是“向上转型”。在继承中我们简单介绍了向上转型,这里就在啰嗦下:在上面的喝酒例子中,酒(Win)是父类,剑南春(JNC)、五粮液(WLY)、酒鬼酒(JGJ)是子类。我们定义如下代码:
JNC a = new JNC();
对于这个代码我们非常容易理解无非就是实例化了一个剑南春的对象嘛!但是这样呢?

Wine a = new JNC();

 在这里我们这样理解,这里定义了一个Wine 类型的a,它指向JNC对象实例。由于JNC是继承与Wine,所以JNC可以自动向上转型为Wine,所以a是可以指向JNC实例对象的。这样做存在一个非常大的好处,在继承中我们知道子类是父类的扩展,它可以提供比父类更加强大的功能,如果我们定义了一个指向子类的父类引用类型,那么它除了能够引用父类的共性外,还可以使用子类强大的功能。

 但是向上转型存在一些缺憾,那就是它必定会导致一些方法和属性的丢失,而导致我们不能够获取它们。所以父类类型的引用可以调用父类中定义的所有属性和方法,对于只存在与子类中的方法和属性它就望尘莫及了。

public class Wine {  
    public void fun1(){  
        System.out.println("Wine 的Fun.....");  
        fun2();  
    }  
       
    public void fun2(){  
        System.out.println("Wine 的Fun2...");  
    }  
}  
   
public class JNC extends Wine{  
    /** 
     * @desc 子类重载父类方法 
     *        父类中不存在该方法,向上转型后,父类是不能引用该方法的 
     * @param a 
     * @return void 
     */  
    public void fun1(String a){  
        System.out.println("JNC 的 Fun1...");  
        fun2();  
    }  
       
    /** 
     * 子类重写父类方法 
     * 指向子类的父类引用调用fun2时,必定是调用该方法 
     */  
    public void fun2(){  
        System.out.println("JNC 的Fun2...");  
    }  
}  
   
public class Test {  
    public static void main(String[] args) {  
        Wine a = new JNC();  
        a.fun1();  
    }  
}

Output:

Wine 的Fun.....  
JNC 的Fun2...  

 从程序的运行结果中我们发现,a.fun1()首先是运行父类Wine中的fun1().然后再运行子类JNC中的fun2()。

 分析:在这个程序中子类JNC重载了父类Wine的方法fun1(),重写fun2(),而且重载后的fun1(String a)与 fun1()不是同一个方法,由于父类中没有该方法,向上转型后会丢失该方法,所以执行JNC的Wine类型引用是不能引用fun1(String a)方法。而子类JNC重写了fun2() ,那么指向JNC的Wine引用会调用JNC中fun2()方法。

所以对于多态我们可以总结如下:

 指向子类的父类引用由于向上转型了,它只能访问父类中拥有的方法和属性,而对于子类中存在而父类中不存在的方法,该引用是不能使用的,尽管是重载该方法。若子类重写了父类中的某些方法,在调用该些方法的时候,必定是使用子类中定义的这些方法(动态连接、动态调用)。

 对于面向对象而已,多态分为编译时多态和运行时多态。其中编辑时多态是静态的,主要是指方法的重载,它是根据参数列表的不同来区分不同的函数,通过编辑之后会变成两个不同的函数,在运行过程中谈不上多态。而运行时多态是动态的,它是通过动态绑定来实现的,也就是我们所说的多态性。

五、String、StringBuffer、StringBuilder区别

1、首先说运行速度,或者说是执行速度,在这方面运行速度快慢为:StringBuilder > StringBuffer > String

String最慢的原因:
 String为字符串常量,而StringBuilder和StringBuffer均为字符串变量,即String对象一旦创建之后该对象是不可更改的,但后两者的对象是变量,是可以更改的。以下面一段代码为例:

1 String str="abc";
2 System.out.println(str);
3 str=str+"de";
4 System.out.println(str);

 如果运行这段代码会发现先输出“abc”,然后又输出“abcde”,好像是str这个对象被更改了,其实,这只是一种假象罢了,JVM对于这几行代码是这样处理的,首先创建一个String对象str,并把“abc”赋值给str,然后在第三行中,其实JVM又创建了一个新的对象也名为str,然后再把原来的str的值和“de”加起来再赋值给新的str,而原来的str就会被JVM的垃圾回收机制(GC)给回收掉了,所以,str实际上并没有被更改,也就是前面说的String对象一旦创建之后就不可更改了。所以,Java中对String对象进行的操作实际上是一个不断创建新的对象并且将旧的对象回收的一个过程,所以执行速度很慢。

 而StringBuilder和StringBuffer的对象是变量,对变量进行操作就是直接对该对象进行更改,而不进行创建和回收的操作,所以速度要比String快很多。

 另外,有时候我们会这样对字符串进行赋值

1 String str="abc"+"de";
2 StringBuilder stringBuilder=new StringBuilder().append("abc").append("de");
3 System.out.println(str);
4 System.out.println(stringBuilder.toString());

 这样输出结果也是“abcde”和“abcde”,但是String的速度却比StringBuilder的反应速度要快很多,这是因为第1行中的操作和String str="abcde";
 是完全一样的,所以会很快,而如果写成下面这种形式

1 String str1="abc";
2 String str2="de";
3 String str=str1+str2;

 那么JVM就会像上面说的那样,不断的创建、回收对象来进行这个操作了。速度就会很慢。
2、再来说线程安全
 在线程安全上,StringBuilder是线程不安全的,而StringBuffer是线程安全的
如果一个StringBuffer对象在字符串缓冲区被多个线程使用时,StringBuffer中很多方法可以带有synchronized关键字,所以可以保证线程是安全的,但StringBuilder的方法则没有该关键字,所以不能保证线程安全,有可能会出现一些错误的操作。所以如果要进行的操作是多线程的,那么就要使用StringBuffer,但是在单线程的情况下,还是建议使用速度比较快的StringBuilder。

3、总结一下
String:适用于少量的字符串操作的情况
StringBuilder:适用于单线程下在字符缓冲区进行大量操作的情况
StringBuffer:适用多线程下在字符缓冲区进行大量操作的情况

六、什么是内部类?内部类的作用

内部类( Inner Class )就定义在另一个类里面或者一个方法里面的类。与之对应,包含内部类的类被称为外部类。

主要作用如下:

  1. 内部类提供了更好的封装,可以把内部类隐藏在外部类之内,不允许同一个包中的其他类访问该类
  2. 内部类的方法可以直接访问外部类的所有数据,包括私有的数据
  3. 内部类所实现的功能使用外部类同样可以实现,只是有时使用内部类更方便

内部类种类:
1、成员内部类(它的定义为位于另一个类的内部)

class Circle {
    private double radius = 0;
    public static int count =1;
    public Circle(double radius) {
        this.radius = radius;
    }
     
    class Draw {     //内部类
        public void drawSahpe() {
            System.out.println(radius);  //外部类的private成员
            System.out.println(count);   //外部类的静态成员
        }
    }
}

2、静态内部类(定义在另一个类里面的类,只不过在类的前面多了一个关键字static,但是它不能使用外部类的非static成员变量或者方法)

public class Test {
    public static void main(String[] args)  {
        Outter.Inner inner = new Outter.Inner();
    }
}
 
class Outter {
    public Outter() {
         
    }
     
    static class Inner {
        public Inner() {
             
        }
    }
}

3、方法内部类(又名局部内部类,它的定义是在一个方法或者一个作用域里面的类,它和成员内部类的区别在于局部内部类的访问仅限于方法内或者该作用域内,而且声明的类不能用public或者private修饰 )

class People{
    public People() {
         
    }
}
 
class Man{
    public Man(){
         
    }
     
    public People getWoman(){
        class Woman extends People{   //局部内部类
            int age =0;
        }
        return new Woman();
    }
}

4、匿名内部类

scan_bt.setOnClickListener(new OnClickListener() {
             
            @Override
            public void onClick(View v) {
                // TODO Auto-generated method stub
                 
            }
        });

七、抽象类和接口区别

1.语法层面上的区别
  1)抽象类可以提供成员方法的实现细节,而接口中只能存在public abstract 方法;
  2)抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是public static final类型的;
  3)接口中不能含有静态代码块以及静态方法,而抽象类可以有静态代码块和静态方法;
  4)一个类只能继承一个抽象类,而一个类却可以实现多个接口。

2.设计层面上的区别
  1)抽象类是对一种事物的抽象,即对类抽象,而接口是对行为的抽象。抽象类是对整个类整体进行抽象,包括属性、行为,但是接口却是对类局部(行为)进行抽象。举个简单的例子,飞机和鸟是不同类的事物,但是它们都有一个共性,就是都会飞。那么在设计的时候,可以将飞机设计为一个类Airplane,将鸟设计为一个类Bird,但是不能将 飞行 这个特性也设计为类,因此它只是一个行为特性,并不是对一类事物的抽象描述。此时可以将 飞行 设计为一个接口Fly,包含方法fly( ),然后Airplane和Bird分别根据自己的需要实现Fly这个接口。然后至于有不同种类的飞机,比如战斗机、民用飞机等直接继承Airplane即可,对于鸟也是类似的,不同种类的鸟直接继承Bird类即可。从这里可以看出,继承是一个 "是不是"的关系,而 接口 实现则是 "有没有"的关系。如果一个类继承了某个抽象类,则子类必定是抽象类的种类,而接口实现则是有没有、具备不具备的关系,比如鸟是否能飞(或者是否具备飞行这个特点),能飞行则可以实现这个接口,不能飞行就不实现这个接口。
  2)设计层面不同,抽象类作为很多子类的父类,它是一种模板式设计。而接口是一种行为规范,它是一种辐射式设计。什么是模板式设计?最简单例子,大家都用过ppt里面的模板,如果用模板A设计了ppt B和ppt C,ppt B和ppt C公共的部分就是模板A了,如果它们的公共部分需要改动,则只需要改动模板A就可以了,不需要重新对ppt B和ppt C进行改动。而辐射式设计,比如某个电梯都装了某种报警器,一旦要更新报警器,就必须全部更新。也就是说对于抽象类,如果需要添加新的方法,可以直接在抽象类中添加具体的实现,子类可以不进行变更;而对于接口则不行,如果接口进行了变更,则所有实现这个接口的类都必须进行相应的改动。

八、抽象类的意义

抽象类的由来:

我们在高度抽象一类事物的时候,由于是提取的此类事物公共行为,但具体到某个具体的事物的时候,同一行为又会有不同的表现。所以我们不应该在抽象阶段就给出具体的实现,而只给出方法的声明,也就是不给出方法体,在java中,没有方法体的方法,我们称之为抽象方法,而类中有抽象方法,类就必须声明为抽象类,抽象类由此而来,而且有其存在的必要性。

抽象类的特点:
包含抽象方法的类称为抽象类,但并不意味着抽象类中只能有抽象方法,它和普通类一样,同样可以拥有成员变量和普通的成员方法。注意,抽象类和普通类的主要有三点区别:

九、抽象类和接口的使用场景介绍

语义上的区别
首先类描述的是 这个东西是什么(强调所属)?包含了静态属性,静态行为 ,属性和行为。
而接口描述的它能做什么事儿(强调行为)? 只是 静态常量属性 和 行为

Interface的应用场合
A. 类与类之前需要特定的接口进行协调,而不在乎其如何实现。
B. 作为能够实现特定功能的标识存在,也可以是什么接口方法都没有的纯粹标识。
C. 需要将一组类视为单一的类,而调用者只通过接口来与这组类发生联系。
D. 需要实现特定的多项功能,而这些功能之间可能完全没有任何联系。

Abstract class的应用场合
一句话,在既需要统一的接口,又需要实例变量或缺省的方法的情况下,就可以使用它。最常见的有:
A. 定义了一组接口,但又不想强迫每个实现类都必须实现所有的接口。可以用abstract class定义一组方法体,甚至可以是空方法体,然后由子类选择自己所感兴趣的方法来覆盖。
B. 某些场合下,只靠纯粹的接口不能满足类与类之间的协调,还必需类中表示状态的变量来区别不同的关系。abstract的中介作用可以很好地满足这一点。
C. 规范了一组相互协调的方法,其中一些方法是共同的,与状态无关的,可以共享的,无需子类分别实现;而另一些方法却需要各个子类根据自己特定的状态来实现特定的功能

十、抽象类是否可以没有方法和属性?

可以。例如 :

abbstract class Test{
}

十一、接口的意义

1、重要性:在Java语言中, abstract class 和interface 是支持抽象类定义的两种机制。正是由于这两种机制的存在,才赋予了Java强大的 面向对象能力。

2、简单、规范性:如果一个项目比较庞大,那么就需要一个能理清所有业务的架构师来定义一些主要的接口,这些接口不仅告诉开发人员你需要实现那些业务,而且也将命名规范限制住了(防止一些开发人员随便命名导致别的程序员无法看明白)。

3、维护、拓展性:比如你要做一个画板程序,其中里面有一个面板类,主要负责绘画功能,然后你就这样定义了这个类。
 可是在不久将来,你突然发现这个类满足不了你了,然后你又要重新设计这个类,更糟糕是你可能要放弃这个类,那么其他地方可能有引用他,这样修改起来很麻烦。
 如果你一开始定义一个接口,把绘制功能放在接口里,然后定义类时实现这个接口,然后你只要用这个接口去引用实现它的类就行了,以后要换的话只不过是引用另一个类而已,这样就达到维护、拓展的方便性。

4、安全、严密性:接口是实现软件松耦合的重要手段,它描叙了系统对外的所有服务,而不涉及任何具体的实现细节。这样就比较安全、严密一些(一般软件服务商考虑的比较多)。

十二、泛型中extends和super的区别

1、<? extends T>限定参数类型的上界:参数类型必须是T或T的子类型
2、<? super T> 限定参数类型的下界:参数类型必须是T或T的超类型

通俗的意思:

extends 示例

static class Food{}
static class Fruit extends Food{}
static class Apple extends Fruit{}
static class RedApple extends Apple{}

List<? extends Fruit> flist = new ArrayList<Apple>();
// complie error:
// flist.add(new Apple());
// flist.add(new Fruit());
// flist.add(new Object());
flist.add(null); // only work for null 

List<? extends Frut> 表示 “具有任何从Fruit继承类型的列表”,编译器无法确定List所持有的类型,所以无法安全的向其中添加对象。可以添加null,因为null 可以表示任何类型。所以List 的add 方法不能添加任何有意义的元素,但是可以接受现有的子类型List<Apple> 赋值。

Fruit fruit = flist.get(0);
Apple apple = (Apple)flist.get(0);

由于,其中放置是从Fruit中继承的类型,所以可以安全地取出Fruit类型。

flist.contains(new Fruit());
flist.contains(new Apple());

在使用Collection中的contains 方法时,接受Object 参数类型,可以不涉及任何通配符,编译器也允许这么调用。

super 示例

List<? super Fruit> flist = new ArrayList<Fruit>();
flist.add(new Fruit());
flist.add(new Apple());
flist.add(new RedApple());

// compile error:
List<? super Fruit> flist = new ArrayList<Apple>();

List<? super Fruit> 表示“具有任何Fruit超类型的列表”,列表的类型至少是一个 Fruit 类型,因此可以安全的向其中添加Fruit 及其子类型。由于List<? super Fruit>中的类型可能是任何Fruit 的超类型,无法赋值为Fruit的子类型Apple的List<Apple>.

// compile error:
Fruit item = flist.get(0);

因为,List<? super Fruit>中的类型可能是任何Fruit 的超类型,所以编译器无法确定get返回的对象类型是Fruit,还是Fruit的父类Food 或 Object.

总结

十三、父类的静态方法能否被子类重写

父类的静态方法可以被子类继承,但是不能重写。
例:

public class Fu {  
    public static void show() {  
        System.out.println("父类的静态方法");  
    }  
    public void method() {  
        System.out.println("父类的一般方法");  
    }  
} 
public class Zi extends Fu {  
    public static void main(String[] args) {  
        Fu fu = new Zi();  
        fu.show();  
        fu.method();  
    }  
    public static void show() {  
        System.out.println("子类的静态");  
    }  
    public void method() {  
        System.out.println("子类的一般方法");  
    }  
      
}

输出
父类的静态方法
子类的一般方法

十四、进程和线程的区别

进程:指在系统中能独立运行并作为资源分配的基本单位,它是由一组机器指令、数据和堆栈等组成的,是一个能独立运行的活动实体。(资源(CPU时间、内存等)分配的最小单位)

线程:线程是进程中的一个实体,作为系统调度和分派的基本单位。Linux下的线程看作轻量级进程。(程序执行的最小单位)

区别:
1、进程是资源分配的最小单位,线程是程序执行的最小单位。

2、进程有自己的独立地址空间,每启动一个进程,系统就会为它分配地址空间,建立数据表来维护代码段、堆栈段和数据段,这种操作非常昂贵。而线程是共享进程中的数据的,使用相同的地址空间,因此CPU切换一个线程的花费远比进程要小很多,同时创建一个线程的开销也比进程要小很多。

3、 线程之间的通信更方便,同一进程下的线程共享全局变量、静态变量等数据,而进程之间的通信需要以通信的方式(IPC)进行。不过如何处理好同步与互斥是编写多线程程序的难点。

4、但是多进程程序更健壮,多线程程序只要有一个线程死掉,整个进程也死掉了,而一个进程死掉并不会对另外一个进程造成影响,因为进程有自己独立的地址空间。

十五、final,finally,finalize的区别

final
final用于修饰类、成员变量和成员方法:

finally
finally通常和try catch搭配使用,保证不管有没有发生异常,资源都能够被释放(释放连接、关闭IO流)。

finalize

finalize是object类中的一个方法,子类可以重写finalize()方法实现对资源的回收。垃圾回收只负责回收内存,并不负责资源的回收,资源回收要由程序员完成,Java虚拟机在垃圾回收之前会先调用垃圾对象的finalize方法用于使对象释放资源(如关闭连接、关闭文件),之后才进行垃圾回收,这个方法一般不会显示的调用,在垃圾回收时垃圾回收器会主动调用。

十六、序列化的方式

十七、Serializable 和Parcelable 的区别

Serializable的使用

import java.io.Serializable;

public class TestSerializable implements Serializable {
    public int old;
    public String name ;
}

Parcelable的使用

public class TestParcelable implements android.os.Parcelable {

    private String name ;
    private int old;

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(android.os.Parcel dest, int flags) {
        dest.writeString(this.name);
        dest.writeInt(this.old);
    }

    public TestParcelable() {
    }

    protected TestParcelable(android.os.Parcel in) {
        this.name = in.readString();
        this.old = in.readInt();
    }

    public static final android.os.Parcelable.Creator<TestParcelable> CREATOR = new android.os.Parcelable.Creator<TestParcelable>() {
        @Override
        public TestParcelable createFromParcel(android.os.Parcel source) {
            return new TestParcelable(source);
        }

        @Override
        public TestParcelable[] newArray(int size) {
            return new TestParcelable[size];
        }
    };
}

1、在使用内存的时候,Parcelable比Serializable性能高,所以推荐使用Parcelable。
2、Serializable在序列化的时候会产生大量的临时变量,从而引起频繁的GC。
3、Parcelable不能使用在要将数据存储在磁盘上的情况,因为Parcelable不能很好的保证数据的持续性在外界有变化的情况下。尽管Serializable效率低点, 也不提倡用,但在这种情况下,还是建议你用Serializable 。

十八、静态属性和静态方法是否可以被继承?是否可以被重写?以及原因?

静态属性、静态方法性都可以被继承和隐藏而不能被重写,因此不能实现多态,不能实现父类的引用可以指向不同子类的对象。非静态方法可以被继承和重写,因此可以实现多态。
静态方法和属性是属于类的,调用的时候直接通过类名.方法名完成对,不需要继承机制及可以调用。如果子类里面定义了静态方法和属性,那么这时候父类的静态方法或属性称之为"隐藏"。如果你想要调用父类的静态方法和属性,直接通过父类名.方法或变量名完成,至于是否继承一说,子类是有继承静态方法和属性,但是跟实例方法和属性不太一样,存在"隐藏"的这种情况。

十九、静态内部类的设计意图

 静态内部类与非静态内部类之间存在一个最大的区别:非静态内部类在编译完成之后会隐含地保存着一个引用,该引用是指向创建它的外围内,但是静态内部类却没有。

没有这个引用就意味着:

  • 它的创建是不需要依赖于外围类的。
  • 它不能使用任何外围类的非static成员变量和方法。

二十、成员内部类、静态内部类、局部内部类和匿名内部类的理解,以及项目中的应用

对内部类的理解与项目中的应用可以回看问题---六、什么是内部类?内部类的作用

二十一、谈谈对kotlin的理解

优点

缺点:
Kotlin虽然非常接近Java,但在许多方面依然不同。因此,想要切换语言的开发者仍然面临着一定的学习曲线。因此,如果你决定将Android开发团队迁移到Kotlin,则需要对培训和时间进行额外的投资。一些使用过Kotlin的开发者反映编译速度较慢,这是Kotlin的主要缺点之一。

不过,在不同的测试环境下结果有所不同:在某些情况下,Kotlin以编译速度击败Java,但有时它又比Java慢得多。虽然Kotlin正在迅速成长为Android开发的首要语言,但目前仍然只有一个小型的开发者社区。这意味着用于学习该语言的资源有限,并且找出在应用开发过程中可能出现的问题的答案会比较困难。

二十二、闭包和局部内部类的区别

闭包

简单理解:
闭包能够将一个方法作为一个变量去存储,这个方法有能力去访问所在类的自由变量。

闭包是 通过“接口与内部类实现的”

package study.ung.edu.ungstudy.javaDemo.testLambda;

public class TestClosure {
    public static void main(String[] args) {
        Food food = new Food();

        food.getEat().eat();
        food.getEat().eat();

        //在内部类是public修饰时,可以通过以下方式
        Food foodPub = new Food();
        EatActive eat = foodPub.new EatActive();
        eat.eat();
    }

}

class Food{
    public static final String name = "Food";
    private static int num = 20;

    public Food() {
        System.out.println("Delicious Food");
    }

    public Active getEat() {
        return new EatActive();
    }

    private class EatActive implements Active {

        @Override
        public void eat() {
            if (num == 0) {
                System.out.println("吃货,已经吃没了");
            }
            num --;
            System.out.println("吃货,你吃了一份了");
        }
    }

    public void currentNum() {
        System.out.println("还剩:"+num+"份");
    }
}

interface Active{

    void eat();
}

而局部内部类定义在方法中,这时,class EatActive在Food类中是不可见的,修改getEat方法体:

public Active getEat() {
        class EatActive implements Active{

            @Override
            public void eat() {
                if (num == 0) {
                    System.out.println("吃货,已经吃没了");
                }
                num --;
                System.out.println("吃货,你吃了一份了");
            }
        }
        return new EatActive();
    }

总结:
1、闭包在类中可见,而在方法内部类不可见。(对类而言,类的定义已经"隐藏"在方法里面了,只能通过方法才能实例化,这种情况就是不可见)

二十三、string 转换成 integer的方式及原理

string 转换成 integer的方式:

String mSmg = "123";
int  m =  Integer.valueOf(mSmg);

原理

    public static Integer valueOf(String s) throws NumberFormatException {
        return Integer.valueOf(parseInt(s, 10));
    }
//该方法的第二个参数表示是基数(最常用的是十进制,还有十六机制,八进制等等).
public static int parseInt(String s, int radix)
                throws NumberFormatException
    {
        /*
         * WARNING: This method may be invoked early during VM initialization
         * before IntegerCache is initialized. Care must be taken to not use
         * the valueOf method.
         */
//如果字符串是空指针,直接抛出异常.
//如果基础小于2或者大于36的话,抛出异常(这种情况一般不会出现,因为我们用的最多就是十进制的了).
        if (s == null) {
            throw new NumberFormatException("null");
        }
//Character.MIN_RADIX=2
        if (radix < Character.MIN_RADIX) {
            throw new NumberFormatException("radix " + radix +
                                            " less than Character.MIN_RADIX");
        }
//Character.MAX_RADIX=36
        if (radix > Character.MAX_RADIX) {
            throw new NumberFormatException("radix " + radix +
                                            " greater than Character.MAX_RADIX");
        }

        int result = 0;//记录返回值
        boolean negative = false;//符号标志
        int i = 0, len = s.length();//i:字符串位置,len:字符串长度
        int limit = -Integer.MAX_VALUE;//界限
        int multmin;//也是一个界限
        int digit;//当前字符表示的数字

        if (len > 0) {
            char firstChar = s.charAt(0);
//用第一个字符来判断是否为正数还是负数
            if (firstChar < '0') { // Possible leading "+" or "-"
                if (firstChar == '-') {
                    negative = true;
                    limit = Integer.MIN_VALUE;
                } else if (firstChar != '+')
                    throw NumberFormatException.forInputString(s);

                if (len == 1) // Cannot have lone "+" or "-"
                    throw NumberFormatException.forInputString(s);
                i++;
            }
            multmin = limit / radix;
            while (i < len) {
                // Accumulating negatively avoids surprises near MAX_VALUE
//这里使用了Character中的静态方法digit,这个方法比较复杂,这里先说明它的功能:
//对于给定的基数,如果是合法的字符(可以转化为数字),返回该数字值,否则返回-1.比如digit('3',10)返回//3,digit('a',10)返回-1.
                digit = Character.digit(s.charAt(i++),radix);
                if (digit < 0) {
                    throw NumberFormatException.forInputString(s);
                }
                if (result < multmin) {
                    throw NumberFormatException.forInputString(s);
                }
                result *= radix;
                if (result < limit + digit) {
                    throw NumberFormatException.forInputString(s);
                }
                result -= digit;
            }
        } else {
            throw NumberFormatException.forInputString(s);
        }
        return negative ? result : -result;
    }
上一篇下一篇

猜你喜欢

热点阅读