(一) java 基础面试知识点 1.java 中==和 equals 和 hashCode 的区别 答案【答案不唯一,可自己衡量】: equals equals 的作用是判断两个对象是否相等。 通过判断两个对象的地址是否相同来判断。 == ==一般用于基本类型数据的比较,判断两个值是否相同 也可以用于类的比较,同样也是比较两个对象的内存地址。(就是两个对 象都是同一个对象) 所以在 equals 没覆盖的情况下,==和 equals 是等价的。 ( 我们可以通过覆盖 equals 来让相同内容的对象来返回 true。) hashcode 能够高效率的产生一个离散的 int 值。 通过 hashCode()来计算出两个对象的 hash 值然后进行比较。 但是会出现不同的类也会有相同 hash 值,所以这不是安全、不可靠的。 总结 总的来说就是一般情况 equals 和==是等价的。 如果两个对象相等,则他们一定是 equals 返回 true。 如果两个对象的 hashCode 相同,也不能 100%保证它们是相同的。
2.int、char、long 各占多少字节数
3.int 与 integer 的区别 答案【答案不唯一,可自己衡量】: 1.Integer 是 int 的包装类,int 则是 Java 的一种基本数据类型 2.Integer 变量必须实例化之后才可以使用,而 int 变量不需要 3.Integer 实际是对象的引用,当 new 一个 Integer 时,实际上是生成一个 指针指向此对象;而 int 则是直接存储数据值 4.Integer 的默认值是 null,int 的默认值是 0
4.谈谈对 java 多态的理解 答案【答案不唯一,可自己衡量】: 打个比方 父亲 person 有行为这个方法,里面包括几个动作:吃饭,睡觉, 走路 父亲有三个儿子,三个儿子都继承了父亲的行为方法,所以三个儿子 都有吃饭,睡觉,走路这些动作,但是三个儿子又分别有自己的动作--大儿 子 A 会弹吉他,二儿子 B 会唱歌,三儿子 C 会打鼓 ... 1.Person person = new A(); 不是父类对象指向子类引用而是父类引用指 向子类对象 2.这个对象不能调用子类 A 特有的弹吉他方法--person.guitar(); X 3.如果仅是这么写程序,还不是多态,记住实现多态的三要素:继承 重写 父 类引用指向子类对象 4.之后,如果你调用 persion.guitar(),此时在代码的编译阶段,persion 调 用的仍然是自己的 guitar(),不是儿子的。而当程序 运行时,就是 java XXX, persion 调用的却是儿子的 guitar()。这个动态的过程才是多态 。 Person person; //父类的引用指向子类的方法; person = new Student(); //person 类型引用做一个判断 //(1)if(person.eat().size==2 ) { if(person instanceof Person) { person.eat(); }else if(person instanceof Student) { Student stu = (Student)person; stu.eat(); } person.eat();//从代码角度看,此时是父类的引用调用的是父类 中的 eat 方法 //(2)子类若覆盖了父类的方法,eat 动态绑定到父类的引用 Person 上,换个名字叫动态绑定 //父类的引用可以调用子类的方法,我们把这一现象成为多态 //从字面意思来理解person这个父类的引用一会是person一会是 student //person 有多种状态; //也叫方法的动态绑定 //继承是通向多态的入口 person.f2(); person.gotobed(); person.eat(); Student stu = new Student(); stu.eat(); stu.gotobed(); //父类的引用能够调用子类的方法 } Java 中,父类的引用既可以指向父类的对象,也可以指向子类的对象。但 子类的引用不能指向父类的对象。引用类型也可以进行类型转换。 但转换的类型一定具有继承关系,即仅允许父子类之间进行转换。 如果尝试将毫无关联的两个类型进行转换,将会引发编译错误。可以使用 instanceof 来判断引用是否为指定的类型。
5.String、StringBuffer、StringBuilder 区别 答案【答案不唯一,可自己衡量】: 我们先对比下 String, StringBuffer, StringBuilder 这三个类。他们的主要区 别一般体现在线程安全和执行效率上。
1.线程安全 String 类是用 final 修饰符修饰的,它的值是不可修改的,因此是线程安全 的。 如果一个 StringBuffer 对象在缓冲区被多个线程使用时,因为 StringBuffer 的方法都是带有 synchronized 关键字的,所以可以保证线程 安全,而 StringBuilder 的方法没有该关键字,不能保证线程安全,因此可 能会出现一些操作错误。多线程情况下建议使用 StringBuffer,单线程建议使 用速度较快的 StringBuilder。 2.执行效率 先看一段代码: String str = "abcdef"; str = str + "123456"; System.out.println(str); 这段代码输出的结果是: “abcdef123456”, 看着好像是 str 被改变了,但 实际上这是一种假象, JVM 对上述代码是这样处理的。 1.执行第一行代码: 新建一个 String 对象“abcdef”(该对象保存在字符串常量池中)将“abcdef”对 象的实例引用赋值给 str(保存在栈中)。 2.执行第二行代码: 再新建一个 String 对象 str,用来执行 str + "123456"操作,也就是说,str 这个对象是没 有发生改变的(String 不可变)。每当用 String 操作字符串时,实际上是在 不断的创建新的对象,而原来的对象就会变为垃圾被GC回收掉,可想而 知这样执行效率会有多低。 一个特殊例子: String str = "This is a" + "special" + "example"; StringBuilder stringBuilder = new StringBuilder("This is a").append("special").append("example"); 你会发现生成 str 对象的速度简直太快了,而这个时候 StringBuilder 速度上 根本一点都不占优势。 其实这是 JVM 的一个把戏,实际上:String str = "This is a" + "special" + "example";其实就是: String str = “This is a special example”;所以不需要太多的时间了。 要注意的是,如果你的字符 串是来自另外的 String 对象的话,速度就没那么快了,譬如: String str2 = "This is a"; String str3 = "special"; String str4 = "test"; String str = str2 +str3 + str4; 这时候 JVM 会规规矩矩的按照原来的方式去做。 总结 1.如果要操作少量的数据用 --> String 2.单线程操作字符串缓冲区 下操作 大量数据 --> StringBuilder 3.多线程操作字符串缓冲区 下操作大量数据 --> StringBuffer
6.什么是内部类?内部类的作用
答案【答案不唯一,可自己衡量】:什么是内部类:
将一个类定义在另一个类里面或者一个方法里面,这样的类称为内部类。
内部类的作用:
1.成员内部类
成员内部类可以无条件访问外部类的所有成员属性和成员方法(包括 private 成员和静态成员)。
当成员内部类拥有和外部类同名的成员变量或者方法时,会发生隐藏现象,即默认情况下访问的是成员内部类的成员。
2.局部内部类
局部内部类是定义在一个方法或者一个作用域里面的类,它和成员内部类的区别在于局部内部类的访问仅限于方法内或者该作用域内。
3.匿名内部类
匿名内部类就是没有名字的内部类
4.静态内部类
指被声明为 static 的内部类,他可以不依赖内部类而实例,而通常的内部类需要实例化外部类,从而实例化。静态内部类不可以有与外部类有相同的类名。不能访问外部类的普通成员变量,但是可以访问静态成员变量和静态方法(包括私有类型)一个 静态内部类去掉 static 就是成员内部类,他可以自由的引用外部类的属性和方法,无论是静态还是非静态。但是不可以有静态属性和方法、
作用
1.每个内部类都能独立的继承一个接口的实现,所以无论外部类是否已经继承了某个(接口的)实现,对于内部类都没有影响。内部类使得多继承的解决方案变得完整,
2.方便将存在一定逻辑关系的类组织在一起,又可以对外界隐藏。
3.方便编写事件驱动程序
4.方便编写线程代码
7.抽象类和接口区别
答案【答案不唯一,可自己衡量】:抽象类是什么:
抽象类不能创建实例,它只能作为父类被继承。抽象类是从多个具体类中抽象出来的父类,它具有更高层次的抽象。从多个具有相同特征的类中抽象出一个抽象类,以这个抽象类作为其子类的模板,从而避免了子类的随意性。
抽象方法只作声明,而不包含实现,可以看成是没有实现体的虚方法抽象类不能被实例化抽象类可以但不是必须有抽象属性和抽象方法,但是一旦有了抽象方法,就一定要把这个类声明为抽象类具体派生类必须覆盖基类的抽象方法抽象派生类可以覆盖基类的抽象方法,也可以不覆盖。如果不覆盖,则其具体派生类必须覆盖它们接口是什么:
接口不能被实例化接口只能包含方法声明接口的成员包括方法、属性、索引器、事件接口中不能包含常量、字段(域)、构造函数、析构函数、静态成员接口和抽象类的区别:
抽象类可以有构造方法,接口中不能有构造方法。抽象类中可以有普通成员变量,接口中没有普通成员变量抽象类中可以包含静态方法,接口中不能包含静态方法一个类可以实现多个接口,但只能继承一个抽象类。接口可以被多重实现,抽象类只能被单一继承如果抽象类实现接口,则可以把接口中方法映射到抽象类中作为抽象方法而不必实现,而在抽象类的子类中实现接口中方法接口和抽象类的相同点:
都可以被继承都不能被实例化都可以包含方法声明派生类必须实现未实现的方法。答案【答案不唯一,可自己衡量】:抽象类往往用来表征对问题领域进行分析、设计中得出的抽象概念,是对一系列看上去不同,但是本质上相同的具体概念的抽象。具体分析如下:
1.因为抽象类不能实例化对象,所以必须要有子类来实现它之后才能使用。这样就可以把一些具有相同属性和方法的组件进行抽象,这样更有利于代码和程序的维护。
比如本科和研究生可以抽象成学生,他们有相同的属性和方法。这样当你对其中某个类进行修改时会受到父类的限制,这样就会提醒开发人员有些东西不能进行随意修改,这样可以对比较重要的东西进行统一的限制,也算是一种保护,对维护会有很大的帮助。
2.当又有一个具有相似的组件产生时,只需要实现该抽象类就可以获得该抽象类的那些属性和方法。
比如学校又新产生了专科生这类学生,那么专科生直接继承学生,然后对自己特有的属性和方法进行补充即可。这样对于代码的重用也是很好的体现。
所以,Java 中抽象类对于代码的维护和重用有很好的帮助,也是 Java 面向对象的一个重要体现。
答案【答案不唯一,可自己衡量】:
应用都是基于规则的应用,也就是基于语法的应用,我们可以根据语法上的异同点来总结抽象类和接口的应用场景
相同点没有什么可说的,我们从不同点下手。
第一个重要的不同点抽象类中不一定都是抽象的方法,也可以有具体实现的方法,这样就可以把大家公用的方法提升到抽象类中,然后具体的方法可以留给子类自己实现(此处经典的应用,模板方法设计模式)。所以抽象类可以更好的实现代码的复用
另一个重要的不同就是类可以实现多个接口。接口和抽象类的概念不一样。这个可以理解为接口是对动作的抽象,抽象类是对根源的抽象(即对本质的抽象与其他类的本质不同)。
抽象类表示的是,这个对象是什么。接口表示的是,这个对象能做什么。比如,男人,女人,这两个类(如果是类的话……),他们的抽象类是人。说明,他们都是人。人可以吃东西,狗也可以吃东西,你可以把“吃东西” 定义成一个接口,然后让这些类去实现它。
所以,在高级语言上,一个类只能继承一个类(抽象类)(正如人不可能同时是生物和非生物),但是可以实现多个接口(吃饭接口、走路接口)。
当你关注一个事物的本质的时候,用抽象类;当你关注一个操作的时候,另一个重要的概念就是多态,多态通过分离做什么和怎么做,从另一个角度将接口和实现分离出来。多态不但能够改善代码的组织结果和可读性,还能创建可扩展的程序----即无论在项目最初创建时还是在需要添加新功能时都可以“生长”的程序。由于接口更关注于动作的实现,多态主要是分离“做什么”和“怎么做”,所以接口的另一个重要的应用就是多态的实现(当然抽象类也可以实现多态,但是接口更加合适)。
抽象类的功能要远超过接口,但是,定义抽象类的代价高。因为高级语言来说(从实际设计上来说也是)每个类只能继承一个类。在这个类中,你必须继承或编写出其所有子类的所有共性。虽然接口在功能上会弱化许多,但是它只是针对一个动作的描述。而且你可以在一个类中同时实现多个接口。在设计阶段会降低难度的。
答案【答案不唯一,可自己衡量】:
答案是肯定的,可以。
抽象类中可以没有抽象方法,但有抽象方法的一定是抽象类。所以,java 中 抽象类里面可以没有抽象方法。注意即使是没有抽象方法和属性的抽象类,也不能被实例化。
答案【答案不唯一,可自己衡量】:
定义接口的重要性:在 Java 编程,abstract class 和 interface 是支持抽象类定义的两种机制。正是由于这两种机制的存在,才使得 Java 成为面向对象的编程语言。定义接口有利于代码的规范:对于一个大型项目而言,架构师往往会对一些主要的接口来进行定义,或者清理一些没有必要的接口。这样做的目的一方面是为了给开发人员一个清晰的指示,告诉他们哪些业务需要实现;同时也能防止由于开发人员随意命名而导致的命名不清晰和代码混乱,影响开发效率。有利于对代码进行维护:比如你要做一个画板程序,其中里面有一个面板类,主要负责绘画功能,然后你就这样定义了这个类。可是在不久将来,你突然发现现有的类已经不能够满足需要,然后你又要重新设计这个类,更糟糕是你可能要放弃这个类,那么其他地方可能有引用他,这样修改起来很麻烦。如果你一开始定义一个接口,把绘制功能放在接口里,然后定义类时实现这个接口,然后你只要用这个接口去引用实现它的类就行了,以后要换的话只不过是引用另一个类而已,这样就达到维护、拓展的方便性。保证代码的安全和严密:一个好的程序一定符合高内聚低耦合的特征,那么实现低耦合,定义接口是一个很好的方法,能够让系统的功能较好地实现,而不涉及任何具体的实现细节。这样就比较安全、严密一些,这一思想一般在软件开发中较为常见。答案【答案不唯一,可自己衡量】:
<? extends T>限定参数类型的上界:参数类型必须是 T 或 T 的子类型
<? super T> 限定参数类型的下界:参数类型必须是 T 或 T 的超类型
总结为:
<? extends T> 只能用于方法返回,告诉编译器此返参的类型的最小继承边界为 T,T 和 T 的父类都能接收,但是入参类型无法确定,只能接受 null 的传入
<? super T>只能用于限定方法入参,告诉编译器入参只能是 T 或其子类型,而返参只能用 Object 类接收
? 既不能用于入参也不能用于返参
答案【答案不唯一,可自己衡量】:
首先答案是不能!
这个问题有两个关键字,一个是静态方法,一个是重写。
我们来先说说重写,可能很多初学的朋友分不清重写和重载的区别。
重写:子类继承父类后,定义了一个和父类中的一模一样方法,这个一模一样是值方法名和参数的定义一模一样。这时候子类要实现这个方法,就称为对父类方法的重写。重载:子类继承父类后,定义了一个和父类中相同名字的方法,但是参数不一样(必须),实现也不同(可选),这就是重载。
静态方法:java 中,static 修饰符修饰的方法就是静态方法。所谓静态就是指:在编译之后所分配的内存会一直存在(不会被回收),直到程序退出内存才会释放这个空间。
在 java 中,所有的东西都是对象,对象的抽象就是类,对于一个类而言,如果要使用他的成员(类中的属性,方法等),一般情况下,必须先实例化对象后,通过对象的引用才能访问这些成员。
但是,如果要使用的成员使用了 static 修饰,就可以不通过实例化获得该成员。
就比如,现在有个桌子,我想吃苹果,一般情况下,我需要先拿个盘子(对象的实例化)去装苹果才能吃到苹果,现在有个苹果直接放在桌子上(用 static 修饰过的静态方法),这样我就可以直接从桌子上拿到苹果。可能大家会有疑问,既然静态方法(能不通过实例化就使用成员)这么方便,为什么不都使用静态方法。
大家回到 static 这个修饰符的功能定义:所谓静态就是指:在编译之后所分配的内存会一直存在(不会被回收),直到程序退出内存才会释放这个空间。
java 的回收机制会定时的回收已经使用过的对象的内存来释放内存给应用程序。如果全部都是静态变量和静态方法,内存都被占用了,java 程序哪里还有运行的空间呢?就好比,你回家就看到桌子上摆满了苹果,那你买的梨子就没地方放了。
现在回到题目中来,父类的静态方法能不能被重写。答案是不能。
因为静态方法从程序开始运行后就已经分配了内存,也就是说已经写死了。所有引用到该方法的对象(父类的对象也好子类的对象也好)所指向的都是同一块内存中的数据,也就是该静态方法。子类中如果定义了相同名称的静态方法,并不会重写,而应该是在内存中又分配了一块给子类的静态方法,没有重写这一说。
答案【答案不唯一,可自己衡量】:
1. 定义进程是一个术语,用来描述一组资源和程序运行所需的内存分配。对于每一个被加载到内存的 exe,在它的生命周期中操作系统会为之创建一个单独隔离的进程。一个进程的失败不会影响其他的进程。每个进程是由私有的虚拟地址空间、代码、数据和其他各种系统资源组成的。
线程是进程的一个实体,是 CPU 调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。系统创建好进程后,实际就启动执行了该进程的主线程,主线程终止了,进程也就随之终止,每一个进程至少有一个线程(即主线程,它无需由用户主动去创建,是由系统在应用程序启动后创建的),用户根据需要在应用程序中创建其他线程,使多个线程并发的运行在同一个进程中。
2.关系
一个线程可以创建和撤销另一个线程;同一个进程中的多个线程之间可以并发执行.
相对进程而言,线程是一个更加接近于执行体的概念,它可以与同进程中的其他线程共享数据,但拥有自己的栈空间,拥有独立的执行序列。
3.区别
进程和线程的主要差别在于它们是不同的操作系统资源管理方式。进程有独立的地址空 间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。
简而言之,一个程序至少有一个进程,一个进程至少有一个线程.线程的划分尺度小于进程,使得多线程程序的并发性高。另外,进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。线程在执行过程中与进程还是有区别的。每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配。这就是进程和线程的重要区别。区别:
final 修饰符(关 bai 键字)。du被 final 修饰的类,就意味着不能再派生出新 zhi 的子类 dao,不能作为父类而被子类继承。因此一个类不能既被 abstract 声明,又被 final 声明。将变量或方法声明为 final,可以保证他们在使用的过程中不被修改。
finally 是在异常处理时提供 finally 块来执行任何清除操作。不管有没有异常被抛出、捕获,finally 块都会被执行。try 块中的内容是在无异常时执行到结束。catch 块中的内容,是在 try 块内容发生 catch 所声明的异常时,跳转到 catch 块中执行。
finalize 是方法名。java 技术允许使用 finalize 方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。这个方法是由垃圾收集器在确定这个对象没有被引用时对这个对象调用的。它是在 object 类中定义的,因此所有的类都继承了它。子类覆盖 finalize 方法以整理系统资源或者被执行其他清理工作。
扩展资料
注意事项
使用 final 方法的原因主要有两个:把方法锁定,以防止继承类对其进行更改。 效率,在早期的 java 版本中,会将 final 方法转为内嵌调用。但若方法过于庞大,可能在性能上不会有多大提升。因此在最近版本中,不需要 final 方法进行这些优化了。final 方法意味着“最后的、最终的”含义,即此方法不能被重写。什么是序列化?
内存中的数据对象只有转换为二进制流才可以进行数据持久化和网络传输。将数据对象转换为二进制流的过程称为对象的序列化(Serialization)。反之,将
二进制流恢复为数据对象的过程称为反序列化(Deserialization)。序列化需要保留充分的信息以恢复数据对象,但是为了节约存储空间和网络带宽,序列化后的二进制流又要尽可能小。序列化常见的使用场景是 RPC 框架的数据传输。
常见的序列化方式有三种:
1.Java原生序列化
Java 类通过实现 Serializable 接口来实现该类对象的序列化,这个接口非常特
殊,没有任何方法,只起标识作用.Java 序列化保留了对象类的元数据(如类、成员变量、继承类信息等),以及对象数据等,兼容性最好,但不支持跨语言,而且性能一般。
实现 Serializable 接口的类建议设置 serialVersionUID 字段值,如果不设置,那么每次运行时,编译器会根据类的内部实现,包括类名、接口名、方法和属性等来自动生成 serialVersionUID。如果类的源代码有修改,那么重新编译后 serial VersionUID 的取值可能会发生变化。因此实现 Serializable 接口的类一定要显
式地定义 serialVersionUID 属性值。修改类时需要根据兼容性决定是否修改 serialVersionUID 值:
1.如果是兼容升级,请不要修改 serialVersionUID 字段,避免反序列化失败。
2.如果是不兼容升级,需要修改 serialVersionUID 值,避免反序列化混乱。
使用 Java 原生序列化需注意,Java 反序列化时不会调用类的无参构造方法,而
是调用 native 方法将成员变量赋值为对应类型的初始值。基于性能及兼容性考虑,不推荐使用 Java 原生序列化。
2.Hessian 序列化
Hessian 序列化是一种支持动态类型、跨语言、基于对象
传输的网络协议。 Java 对象序列化的二进制流可以被其他语言( 如 C++、
Python )反
序列化。 Hessian 协议具有如下特性.
自描述序列化类型。不依赖外部描述文件或接口定义 , 用一个字节表示常用基础类型 , 极大缩短二进制流。
· 语言无关,支持脚本语言。
· 协议简单,比 Java 原生序列化高效。
相比 Hessian 1.0, Hessian 2.0 中增加了压缩编码,其序列化二进制流大小是
Java
序列化的 50% ,序列化耗时是 Java 序列化的 30% ,反序列化耗时是 Java
反序列化的
20% 。
Hessian 会把复杂对象所有属性存储在一个 Map 申 进行序列化。所以在父类、子
类存在同名成员变量的情况下, Hessian 序列化时,先序列化子类 ,然后序
列化父类,因此反序列化结果会导致子类同名成员变量被父类的值覆盖。
阿里首选的序列化方式
3.Json序列化
JSON ( JavaScript O 同 ect Notation )是一种轻量级的数据交
换格式。 JSON 序列化就是将数据对象转换为 JSON 字符串。在序列化过程
中抛弃了类型信息,所以反序列化时只有提供类型信息才能准确地反序列化。相比前两种方式,
JSON 可读性比较好,方便调试。
序列化通常会通过网络传输对象 , 而对象中往往有敏感数据,所以序列化常常成为黑客的攻击点,攻击者巧妙地利用反序列化过程构造恶意代码,使得程序在反序
列化的过程中执行任意代码。 Java 工程中广泛使用的 Apache Commons
Collections 、
Jackson 、 fastjson 等都出现过反序列化漏洞。如何防范这种黑客攻击呢?有些对象的
敏感属性不需要进行序列化传输 ,可以加 transient 关键字,避免把此属性信
息转化为序列化的二进制流。如果一定要传递对象的敏感属性,可以使用对称与非对称加密方式独立传输,再使用某个方法把属性还原到对象中。应用开发者对序列化要有一定的安全防范意识 ,对传入数据的内容进行校验或权限控制,及时更新安全漏洞,避免受
到攻击。
序列化
什么是序列化
Serializable
. 实现了 Serializable 接口之后,Eclipse 就会提示你增加一个 serialVersionUID,虽然不加的话上述程序依然能够正常运行
. 序列化 ID 在 Eclipse 下提供了两种生成策略
一个是固定的 1L一个是随机生成一个不重复的 long 类型数据(实际上是使用 JDK 工具,根据类名、接口名、成员方法及属性等来生成). 如果是通过网络传输的话,如果 Person 类的 serialVersionUID 不一致,那么反序列化就不能正常进行。例如在客户端 A 中 Person 类的 serialVersionUID=1L,而在客户端 B 中 Person 类的 serialVersionUID=2L 那么就不能重构这个 Person 对象
Parcelable
. Parcel
在介绍之前我们需要先了解 Parcel 是什么?Parcel 翻译过来是打包的意思,其实就是包装了我们需要传输的数据,然后在 Binder 中传输,也就是用于跨进程传输数据简单来说,Parcel 提供了一套机制,可以将序列化之后的数据写入到一个共享内存中,其他进程通过 Parcel 可以从这块共享内存中读出字节流,并反序列化成对象,下图是这个过程的模型
Parcel 可以包含原始数据类型(用各种对应的方法写入,比如 writeInt(),writeFloat()等),可以包含 Parcelable 对象,它还包含了一个活动的 IBinder 对象的引用,这个引用导致另一端接收到一个指向这个 IBinder 的代理 IBinder。
Parcelable 通过 Parcel 实现了 read 和 write 的方法,从而实现序列化和反序列化
. 序列化
实现 Parcelable 的作用
永久性保存对象,保存对象的字节序列到本地文件中;通过序列化对象在网络中传递对象;通过序列化在进程间传递对象。首先写一个类实现 Parcelable 接口,会让我们实现两个方法:
describeContents 描述
其中 describeContents 就是负责文件描述.通过源码的描述可以看出,只针对一些特殊的需要描述信息的对象,需要返回 1,其他情况返回 0 就可以
writeToParcel 序列化
我们通过 writeToParcel 方法实现序列化,writeToParcel 返回了 Parcel,所以我们可以直接调用 Parcel 中的 write 方法,基本的 write 方法都有,对象和集合比较特殊下面单独讲,基本的数据类型除了 boolean 其他都有,Boolean 可以使用 int 或 byte 存储
我们将上面的 Album 对象实现序列化,Album 对象包含四个字段。
. 反序列化
反序列化需要定义一个 CREATOR 的变量,上面也说了具体的做法,这里可以直接复制 Android 给的例子中的,也可以自己定义一个(名字千万不能改),通过匿名内部类实现 Parcelable 中的
Creator 的接口
. Parcelable 的使用和实现根据上面三个过程的介绍,Parcelable 就写完了,就可以直接在 Intent 中传输了,可以自己写两个 Activity 传输一下数据试一下,其中一个 putExtra 另一个 getParcelableExtra 即可
Parcelable和Serializable的区别和比较
Parcelable 和 Serializable 都是实现序列化并且都可以用于 Intent 间传递数据,Serializable 是 Java 的实现方式,可能会频繁的 IO 操作,所以消耗比较大,但是实现方式简单 Parcelable 是 Android 提供的方式,效率比较高,但是实现起来复杂一些 , 二者的选取规则是:内存序列化上选择 Parcelable, 存储到设备或者网络传输上选择 Serializable(当然 Parcelable 也可以但是稍显复杂)
选择序列化方法的原则
. 在使用内存的时候,Parcelable 比 Serializable 性能高,所以推荐使用 Parcelable。
. Serializable 在序列化的时候会产生大量的临时变量,从而引起频繁的 GC。
. Parcelable 不能使用在要将数据存储在磁盘上的情况,因为 Parcelable 不能很好的保证数据的持续性在外界有变化的情况下。尽管 Serializable 效率低点,但此时还是建议使用
Serializable 。
java 中静态属性和静态方法可以被继承,但是没有被重写(overwrite)而是被隐
藏.
原因:
1). 静态方法和属性是属于类的,调用的时候直接通过类名.方法名完成对,不需要继承机制及可以调用。如果子类里面定义了静态方法和属性,那么这时候父类的静态方法或属性称之为"隐藏"。如果你想要调用父类的静态方法和属性,
直接通过父类名.方法或变量名完成,至于是否继承一说,子类是有继承静态方法和属性,但是跟实例方法和属性不太一样,存在"隐藏"的这种情况。
2). 多态之所以能够实现依赖于继承、接口和重写、重载(继承和重写最为关键)。有了继承和重写就可以实现父类的引用指向不同子类的对象。重写的功能是:"重写"后子类的优先级要高于父类的优先级,但是“隐藏”是没有这个优先级之分的。
3). 静态属性、静态方法和非静态的属性都可以被继承和隐藏而不能被重写,因此不能实现多态,不能实现父类的引用可以指向不同子类的对象。非静态方法可以被继承和重写,因此可以实现多态。
静态属性和静态方法可以被继承
静态方法不可以被重写,不能实现多态
内部类
内部类,即定义在一个类的内部的类。为什么有内部类呢?我们知道,在 java 中类是单继承的,一个类只能继承另一个具体类或抽象类(可以实现多个接口)。这种设计的目的是因为在多继承中,当多个父类中有重复的属性或者方法时,子类的调用结果会含糊不清,因此用了单继承。
而使用内部类的原因是:每个内部类都能独立地继承一个(接口的)实现,所以无论外围类是否已经继承了某个(接口的)实现,对于内部类都没有影响。
在我们程序设计中有时候会存在一些使用接口很难解决的问题,这个时候我们可以利用内部类提供的、可以继承多个具体的或者抽象的类的能力来解决这些程序设计问题。可以这样说,接口只是解决了部分问题,而内部类使得多重继承的解决方案变得更加完整。
静态内部类
说静态内部类之前,先了解下成员内部类(非静态的内部类)。
成员内部类成员内部类也是最普通的内部类,它是外围类的一个成员,所以它可以无限制的访问外围类的所有成员属性和方法,尽管是 private 的,但是外围类要访问内部类的成员属性和方法则需要通过内部类实例来访问。
在成员内部类中要注意两点:
成员内部类中不能存在任何 static 的变量和方法;
成员内部类是依附于外围类的,所以只有先创建了外围类才能够创建内部类。
静态内部类静态内部类与非静态内部类之间存在一个最大的区别:非静态内部类在编译完成之后会隐含地保存着一个引用,该引用是指向创建它的外围内,但是静态内部类却没有。
没有这个引用就意味着:
它的创建是不需要依赖于外围类的。
它不能使用任何外围类的非 static 成员变量和方法。
其它两种内部类:局部内部类和匿名内部类局部内部类局部内部类是嵌套在方法和作用于内的,对于这个类的使用主要是应用与解决比较复杂的问题,想创建一个类来辅助我们的解决方案,到那时又不希望这个类是公共可用的,所以就产生了局部内部类,局部内部类和成员内部类一样被编译,只是它的作用域发生了改变,它只能在该方法和属性中被使用,出了该方法和属性就会失效。
匿名内部类
匿名内部类是没有访问修饰符的。new 匿名内部类,这个类首先是要存在的。当所在方法的形参需要被匿名内部类使用,那么这个形参就必须为 final。匿名内部类没有明面上的构造方法,编译器会自动生成一个引用外部类的构造方法。放在一个类的内部的类我们就叫内部类。
成员内部类定义在类内部的非静态类,就是成员内部类。静态内部类:定义在类内部的静态类,就是静态内部类。
应用场景:
Java 集合类 HashMap 内部就有一个静态内部类 Entry。Entry 是 HashMap 存放元素的抽象,HashMap 内部维护 Entry 数组用了存放元素,但是 Entry 对使用者是透明的。像这种和外部类关系密切的,且不依赖外部类实例的,都可以使用静态内部类。
局部内部类定义在方法中的类,就是局部类。(局部内部类是嵌套在方法和作用于内的,对于这个类的使用主要是应用与解决比较复杂的问题,想创建一个类来辅助我们的解决方案,到那时又不希望这个类是公共可用的,所以就产生了局部内部类,局部内部类和成员内部类一样被编译,只是它的作用域发生了改变,它只能在该方法和属性中被使用,出了该方法和属性就会失效。)
}
应用场景:
如果一个类只在某个方法中使用,则可以考虑使用局部类。
匿名内部类匿名内部类是没有访问修饰符的。
. new 匿名内部类,这个类首先是要存在的。
. 当所在方法的形参需要被匿名内部类使用,那么这个形参就必须为 final。
. 匿名内部类没有明面上的构造方法,编译器会自动生成一个引用外部类的构造方法。
Kotlin 是一个基于 JVM 的新编程语言。
Kotlin 的愿景是:用同一种语言,桥接多平台的不同应用的端对端开发。包括
全栈 Web 应用、Android 和 iOS 客户端、嵌入式/物联网等等。Android
Studio 3.0 预览版已支持 Kotlin,,在「Code」菜单中选择「Convert Java File to Kotlin File」。AS 会添加 Kotlin 依赖,然后把 Java 代码转成同等功能的
Kotlin 代码。
简洁它大大减少了代码的数量,阅读更加简洁
安全避免空指针异常等整个类的错误。
通用构建服务器端程序、Android 应用程序或者在浏览器中运行的前端程序。
互操作性通过 100% Java 互操作性,利用 JVM 既有框架和库。
. 局部内部类就像是方法里面的一个局部变量一样,是不能有 public、protected、private 以及 static 修饰符的。
. 闭包(Closure)是一种能被调用的对象,它保存了创建它的作用域的信息。JAVA 并不能显式地支持闭包,但是在 JAVA 中,闭包可以通过“接口+内部类”来实现。
例如:一个接口程序员和一个基类作家都有一个相同的方法 work,相同的方法名,但是其含义完全不同,这时候就需要闭包。
23、什么是单例设计模式
java模式之单例模式:23
单例模式确保一个类只有一个实例5261 ,自行提供这个实例并向整4102 个系统提供这个1653 实例。特点:
1,一个类只能有一个实例
2,自己创建这个实例
3,整个系统都要使用这个实例例: 在下面的对象图中,有一个"单例对象",而"客户甲"、"客户乙" 和"客户丙 "是单例对象的三个客户对象。可以看到,所有的客户对象共享一个单例对象。
而且从单例对象到自身的连接线可以看出,单例对象持有对自己的引用。
Singleton模式主要作用是保证在Java应用程序中,一个类Class只有一个实例存在。在很多操作中,比如建立目录 数据库连接都需要这样的单线程操作。
一些资源管理器常常设计成单例模式。
外部资源:譬如每台计算机可以有若干个打印机,但只能有一个PrinterSpooler, 以避免两个打印作业同时输出到打印机中。每台计算机可以有若干个通信端口,系统应当集中管理这些通信端口,以避免一个通信端口被两个请求同时调用。内部资源,譬如,大多数的软件都有一个(甚至多个)属性文件存放系统配置。这样的系统应当由一个对象来管理这些属性文件。
一个例子:Windows 回收站。在整个视窗系统中,回收站只能有一个实例,整个系统都使用这个惟一的实例,而且回收站自行提供自己的实例。因此,回收站是单例模式的应用。
两种形式: 1,饿汉式单例类
public class Singleton { private Singleton(){}
//在自己内部定义自己一个实例,是不是很奇怪?
//注意这是private 只供内部调用
private static Singleton instance = new Singleton();
//这里提供了一个供外部访问本class的静态方法,可以直接访问
public static Singleton getInstance() { return instance;
}
}
2,懒汉式单例类
public class Singleton { private static Singleton instance = null; public static synchronized Singleton getInstance() {
//这个方法比上面有所改进,不用每次都进行生成对象,只是第一次
//使用时生成实例,提高了效率!
if (instance==null) instance=new Singleton(); return instance; }
}
第二中形式是lazy initialization,也就是说第一次调用时初始Singleton,
以后就不用再生成了
Character.digit(char ch, int radix)方法返回指定基数中字符表示的数值。总结
. parseInt(String s)--内部调用 parseInt(s,10)(默认为 10 进制)
. 正常判断 null,进制范围,length 等
. 判断第一个字符是否是符号位
. 循环遍历确定每个字符的十进制值
. 通过*= 和-= 进行计算拼接
. 判断是否为负值 返回结果。