Android知识点总结(二):Java核心基础汇总

52 阅读7分钟

1、Java中抽象类和接口,在开发中如何去选择?

  • 抽象类的设计目的是代码复用,接口的目的是对行为进行约束
  • 当is-A的关系,用抽象类。has-A的关系,用接口
  • 比如我定义一个抽象类是Dog,可以有具体方法,sleep(),抽象方法eat()。那么新建一个握手的小狗ShakeHandDog,继承Dog。那么我就可以复用sleep的内容了,不用重新写了。eat方法我想交给子类定义,他们自己玩就好了。如果ShakehandDog是抽象就可以不实现抽象方法,否则必须实现。
  • ShakeHandDog能够握手,握手这个功能,并不是狗天生就有的,所以我定义一个IHandshake的接口,有个doShakeHand方法,让ShakeHandDog实现这个接口,约束它必须有这个doShakeHand方法。
  • 具体区别
    • 抽象 abstract关键字、单继承。修饰符除了private都能有。可以有构造方法,具体方法抽象方法。子类是抽象类则可以不实现从抽象方法,某则必须申明方法的实现。目的是重用
    • 接口 inerface关键字、接口可以继承1个或多个接口。子类也能实现多个接口。修饰符默认public,只有这个。没有构造方法,没有具体方法。只有抽象方法。实现类要实现所有申明的方法。核心是“约束,降低耦合”

2、重载和重写是什么意思,区别是什么?

  • 重载是同一个类,可以存在多个同名但是参数内容或者顺序不一样的方法。
  • 重写是重新写,父类的方法对子类不适用或者要增强时,子类可以对父类中继承的方法进行重写。

3、 什么是多态?

  • 多态指的是,同一方法或者对象在不同情况下表现出不同的行为。
  • 子类可以重写父类的方法。那么同一方法会表现不同的行为。
class Animal {
    public void makeSound() {
        System.out.println("Some sound");
    }
}

class Dog extends Animal {
    @Override
    public void makeSound() {
        System.out.println("Bark");
    }
}

class Cat extends Animal {
    @Override
    public void makeSound() {
        System.out.println("Meow");
    }
}

Animal animal1 = new Dog();
Animal animal2 = new Cat();
animal1.makeSound(); // output: "Bark"
animal2.makeSound(); // output: "Meow"
  • 同一类,不同方法参不同而导致的重载,那么也会导致同方法,不同行为。

4、静态内部类是什么?和非静态内部类的区别是什么?

  • 在一类的内部再写一个类,就是非静态内部类,将这个类用static声明就是静态内部类
  • 非静态内部类可以访问外部类的静态和非静态 成员。静态内部类只能访问外部类的静态成员
  • 这个在反编译的时候可以看见,非静态内部类如果有引用了外部内,会直接持有外部类。 image.png

image.png

  • 这个呢也是handler引起内存泄漏的原因。handler如果消息延迟,那么handleMessage()方法如果有用外部类的非静态方法或者非静态成员。必定会持有外部类。那么可达性分析来说handler.handleMessage()持有activity,Messagequeue的message持有handler(target时handler),messagequeue被looper持有,looper被threadlocal持有。threadlocal当前线程持有,那么根可达。所以activity是不会被释放低,所以造成内存泄漏。
  • 解决方案1。静态内部类+弱引用。当activity被 ams持有的时候 ,弱引用是不会断的,当activity被ams放弃,那么一旦gc,弱引用就断了,activity就被回收了。 private static class MyHandler extends Handler{ private final WeakReference<MineActivity> mMineActivityWeak; public MyHandler(MineActivity mineActivity){ mMineActivityWeak = new WeakReference<>(mineActivity); } @Override public void handleMessage(@NonNull Message msg) { super.handleMessage(msg); MineActivity mineActivity = mMineActivityWeak.get(); if(mineActivity != null){ mineActivity.number = 5; } } }
  • activity ondestory时清除handler的message。handler.removeCallbackAndMessages(null)

4、java传参是值传递还是引用传递?

  • 值传递:传递的参数是拷贝的值
  • 引用传递:传递的是引用的地址
  • 基本数据类型是值传递,拷贝一个值给执行的方法的参数,只是一个副本。
  • 引用类型的参数,比如我新建一个object,id=10。会拷贝一个引用,objectParam给到方法的参数。如果方法内objectParam.id = 9,那么object.id会等于9。但是如果 objectParam = new Object()。那么object不受影响。因为引用的那个盒子里指向的堆中对象已经变了。
  • 所以,都是值传递,没有引用传递。

5、使用equals和==进行比较的区别

  • ==
    • 基本数据类型比较的是值
    • 引用数据类型比较的是地址
  • equals
    • 本质上还是在内部用==实现的
    • 但有些方法会重写,比如String integer 。都写成了值比较。String是逐个字符串比较
public class StringDemo {
    public static void main(String args[]) {
        String str1 = "Hello";

        String str2 = new String("Hello");

        String str3 = str2; // 引用传递

        System.out.println(str1 == str2); // false

        System.out.println(str1 == str3); // false

        System.out.println(str2 == str3); // true

        System.out.println(str1.equals(str2)); // true

        System.out.println(str1.equals(str3)); // true

        System.out.println(str2.equals(str3)); // true
        }
    }

  image.png

  • 当然上图其实描述不太准确,准确来说是,String str2 = new String("Hello");会在堆和方法常量池中各创建一个对象。String str = "Hello";只会创建一个对象,指向到方法去中的字符串常量池。一个是堆中的,一个是方法区常量池的,当然不一样。

6、String s = new String("xxx");创建几个对象? String s2 = "xxxxx";创建几个对象 ?

  • String s = new String("xxx");首先堆中会创建一个String对象,如果方法区中的常量池中不包含"xxx",那么会在其中创建一个"xxx"对象,共创建2个对象。如果存在就不创建了,那就一个对象。最后栈上的引用s指向,堆中的String对象,再指向常量池中的“xxx”对象。
  • 同理,如果"xxxxx"在常量池中存在就不会创建对象,不存在就创建一个"xxxxx"对象。
  • image.png

7、finally中的代码一定会执行吗?try里有return,finally还执行么?

  • 一般情况下,会执行。try里有return,会吧return的值存在栈帧的局部变量表中,然后执行finally语句块,最后再从局部变量表中取回return的值返回。另外,如果try和finally中都有return,会用finally中的return,忽略try中的。
  • 特殊情况下,
    • finally之前那调用system.exit。或者再try中被kill。
    • 在守护线程中,其它线程都执行完了,守护线程需要立刻停止,退出虚拟机,finally也有可能不执行

8、Java异常机制中,异常Exception与错误Error的区别。

  • 两者都继承与Throwable
  • Error是程序不能处理的错误,严重,比如OutOfMemmoryError,发生时,虚拟机一会中断线程。绝大多数程序运行是不被允许这种情况
  • Exception,是程序可以处理的异常,分为运行时异常和非运行时异常
    • 运行时异常,又称不受检查异常。java编译是不会告诉我有这个异常,运行时才会暴露出来
    • 非运行时异常,,比如IOException、Sqlexception,必须处理,try catch或者throw。否则就不能编译通过

9、Parcelable,Serializable的区别

  • serializable是Java提供的序列化机制,都能在Intent中传输
    • 序列化就是将对象转为可存储或者传输形式的过程。序列化期间,对象被写入到临时或者永久性存储区,反序列化则是将存储中数据,创新创建为对象。
  • Parcelable则是Android提供的适合于内存那种传输的序列化方式。
  • 速度上Parcelable更快
    • Serializable使用I/O读取存储在硬盘上。Parcelable直接内存中读写
    • Serializable使用反射,序列化反序列化需要大量I/0。Parcelable自己实现封送/解封,不要反射,数据存在native内存中更快
  • 使用上Parcelable更复杂
    • Serializable只要实现Serializable接口
    • Parcelable不仅仅是实现Parcelable接口,还需要实现一些方法。
  • Android内存见传输数据用Parcelable,网络就用Serializable

10、为什么Intent传递对象要序列化?

  • Intent传递数据本质上是使用Binder来完成的。Intent启动组件要借助AMS来完成,因此startActivity会离开当前进程到AMS所在的system_server进程来跨进程通信。那么对象就要在不同进程直接进行传输。
  • 为了保护各个进程互相不干扰,进程隔离让,system_server进程无法直接获取应用进程中的对象。
  • serializable是将对象序列化为二进制文件后进行传递。
  • parcelable,本质上是将数据放到Parcel当中,如果是不同空间,举例,传递对象student然后把各个字段拆解拷贝到,parcel对象,再把parcel拷贝到内核空间,进程B从内核空间读取数据,得到parcel对象,根据其字段创建一个新的student。看起来就像是student对象从a到b。