这文章写的好像套娃,觉得挺有意思的就这么写下去了
1.面对对象的基本特征
面向对象的三个基本特征是:封装、继承和多态。
继承:让某个类型的对象获得另一个类型的对象的属性的方法。继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法,或子类从父类继承方法,使得子类具有父类相同的行为。
封装:隐藏部分对象的属性和实现细节,对数据的访问只能通过外公开的接口。通过这种方式,对象对内部数据提供了不同级别的保护,以防止程序中无关的部分意外的改变或错误的使用了对象的私有部分。
多态:对于同一个行为,不同的子类对象具有不同的表现形式。多态存在的3个条件:1)继承;2)重写;3)父类引用指向子类对象。
举个例子就是都睡觉,猫是趴着睡,而马是站着睡。
2.Java 内存结构(运行时数据区)这里会涉及到一些jvm得知识具体可以看我关于jvm得文章
程序计数器:线程私有。一块较小的内存空间,可以看作当前线程所执行的字节码的行号指示器。如果线程正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是Native方法,这个计数器值则为空。
Java虚拟机栈:线程私有。它的生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。
本地方法栈:线程私有。本地方法栈与虚拟机栈所发挥的作用是非常相似的,它们之间的区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的Native方法服务。
Java堆:线程共享。对大多数应用来说,Java堆是Java虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。
方法区:与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息(构造方法、接口定义)、常量、静态变量、即时编译器编译后的代码(字节码)等数据。方法区是JVM规范中定义的一个概念,具体放在哪里,不同的实现可以放在不同的地方。
运行时常量池:运行时常量池是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池,用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。
String str = new String("hello");
上面的语句中变量 str 放在栈上,用 new 创建出来的字符串对象放在堆上,而"hello"这个字面量是放在堆中。
3.类加载的过程
类加载的过程包括:加载、验证、准备、解析、初始化,其中验证、准备、解析统称为连接。
加载:通过一个类的全限定名来获取定义此类的二进制字节流,在内存中生成一个代表这个类的java.lang.Class对象。
验证:确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
准备:为静态变量分配内存并设置静态变量初始值,这里所说的初始值“通常情况”下是数据类型的零值。
解析:将常量池内的符号引用替换为直接引用。
初始化:到了初始化阶段,才真正开始执行类中定义的 Java 初始化程序代码。主要是静态变量赋值动作和静态语句块(static{})中的语句。
4.接口和抽象类的区别是什么?
接口 | 抽象类 |
接口可以多实现(类可以实现很多个接口) | 抽象类只能单继承(只能继承一个抽象类) |
接口必须使用 implements 来实现接口 | 抽象类的子类使用 extends 来继承 |
接口不可以有 | 抽象类可以有构造函数(构造方法) |
接口中的方法默认使用 public 修饰 | 抽象类中的方法可以是任意访问修饰符(Java 8 之前接口中的方法只能是 public 类型,Java9 支持 private 类型。 ) |
接口要实现接口需要实现所有方法 | 抽象类不一定 |
接口中没有成员变量,只能有常量(默认就是public static final) | 抽象类可以有成员变量 |
抽象类可以有非抽象的方法(default 方法、静态方法等。Java 9 支持私有方法、私有静态方法。) | |
接口不可以使用new实例化但可以声明,但是必须引用一个实现该接口的对象 |
设计思想的区别:
接口是自上而下的抽象过程,接口规范了某些行为,是对某一行为的抽象。我需要这个行为,我就去实现某个接口,但是具体这个行为怎么实现,完全由自己决定。
抽象类是自下而上的抽象过程,抽象类提供了通用实现,是对某一类事物的抽象。我们在写实现类的时候,发现某些实现类具有几乎相同的实现,因此我们将这些相同的实现抽取出来成为抽象类,然后如果有一些差异点,则可以提供抽象方法来支持自定义实现。
5.什么是双亲委派模型?
如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。(首先是应用程序加载器,然后是扩展类加载器最后是启动类(根)加载器)也就是说如果你自己定义了一个java.lang.String类。这样是肯定会报错的,他并不会执行你定义的,而是会最终执行根加载器的。(就不细说了)
6.Java虚拟机中有哪些类加载器?(将一个类实例化为一个对象)
启动类加载器(Bootstrap ClassLoader):
这个类加载器负责将存放在<JAVA_HOME>\lib目录中的,或者被-Xbootclasspath参数所指定的路径中的,并且是虚拟机识别的(仅按照文件名识别,如rt.jar,名字不符合的类库即使放在lib目录中也不会被加载)类库加载到虚拟机内存中。
扩展类加载器(Extension ClassLoader):
这个加载器由sun.misc.Launcher$ExtClassLoader实现,它负责加载<JAVA_HOME>\lib\ext目录中的,或者被java.ext.dirs系统变量所指定的路径中的所有类库,开发者可以直接使用扩展类加载器。
应用程序类加载器(Application ClassLoader):
这个类加载器由sun.misc.Launcher$AppClassLoader实现。由于这个类加载器是ClassLoader中的getSystemClassLoader()方法的返回值,所以一般也称它为系统类加载器。它负责加载用户类路径(ClassPath)上所指定的类库,开发者可以直接使用这个类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。
自定义类加载器:用户自定义的类加载器。
虚拟机自带的加载器。
7.八种基本数据类型分别是:
4种整数类型(byte、short、int、long);
2种浮点类型(float、double);
1种字符类型“char”;
1种布尔类型“boolean”。
String不是基本数据类型!!!!!!
String不是基本数据类型!!!!!!
String不是基本数据类型!!!!!!
除了基本数据类型,其他的都是引用数据类型。
8.基本数据类型:数据直接存储在栈上
引用数据类型:数据存储在堆上,栈上只存储引用地址。
栈上存储的东西:八大基本数据类型+引用地址+实例方法
栈是先进后出,队列是先进先出FIFO(first input first output )
喝多了吐就是栈,吃多了拉就是队列。栈就好像一个桶,先进去的被压在下边会后出。
9.那我们来想一下 String s=new String("xyz")创建了几个字符串对象?
答案是一个或者两个
如果字符串常量池中已经有xyz则是一个,没有则是两个,当字符串常量池没有xyz,会创建一个字符串字面xyz所对应,驻留在一个全局共享的字符串常量池中的实例,该实例在堆中,字符串常量池只放引用。
还有一个通过new String ()创建并初始化的,内容与xyz相同的实例,也是放在常量池中。
10.那么我们再思考一下String s="xyz",与String s=new String("xyz")有什么区别?
两者都会先看常量池检查是否存在xyz,如果有就直接用,没有就创建。但是String s=new String("xyz")还会通过new String 在堆里创建一个内容与xyz相同的实例。jdk1.8后常量池在元空间里,并不在堆内存里。
String 是在类加载阶段加载到元空间Class类型 创建出来的
11.那么我们在深入理解一下
String A="abc"; String B="abc"; String C=new String("abc"); String D=new String("abc");
A==B?
C==D?
答案是 true 和false
因为c和d是new出来的两个不同的对象,他们会在堆里创建一个与abc相同的实例,地址不一样。
a和b就是比较的常量池里的地址。
12.再来一题
Integer a=128;Integer b=128;Integer c=127;Integer d=127;
a==b?
c==d?
答案是false true
执行Integer a=128;相当于执行:Integer a=Integer.valueOf(128),这是一个自动装箱的过程。(将int类型的128转换为Integer类型)
Integer中引入了IntegerCache来缓存一定范围内的值,范围为-128~127,127命中了,所以是相同对象,128没命中所以是不同对象。这个值是可以修改的,在jvm启动参数中。
public class Test {
public static void main(String[] args){
int a = 3;
triple(a);
System.out.println(a);
}public static void triple(int x) {
x = 3 * x;
}
}
大家可能都会想,这个时候a的值变成了9,但其实还是3,因为java只存在值传递,同时包装类一旦创建其数值是不能改变的。所有不可能实现一个方法改变一个数据类型的值。
这个时候如果我们需要改变这个值,那就需要使用持有者类型。包括IntHolder、BooleanHolder等。每个持有者类型都包含一个共有域值,通过它可以访问储存在其中的值。
public class Test {
public static void main(String[] args){
IntHolder a = new IntHolder();
a.value = 3;
triple(a);
System.out.println(a.value);
}public static void triple(IntHolder x) {
x.value = 3 * x.value;
}
}
13.今天理解了一下i++和++i,让我们逐渐变态起来吧
int i=0;
i=i++;
System.out.print(i);
输出i应该是多少?
int a=2;
int b=(3*a++)+a;
System.out.print(b);
int a=2;
int b=a+(3*a++);
System.out.print(b);
int i=1;
int j=1;
int k=i++ + ++i + ++j + j++;
System.out.print(k);
int a=0; int b=0; a=a++; b=a++; System.out.println("a="+a+",b="+b);
int a=0;
int b=0;
int c=2;
a=a++;(这个时候a应该等于0,是没有任何争议的吧,所以a=0)
c=a++;(然后这个时候a先自增为1,然后返回自己之前的值,也就是将0返回,c=0)
b=a++;(这个时候a为1,然后1自增为2,将之前的值1返回,所以b=1,a为2)
System.out.println("a="+a+",b="+b+",c="+c);
看到这些题逐渐变态起来有点于心不忍,前边都还比较简单,最后两题会详细解答的 答案是
0
9
8
8
a=1,b=0,
a=2,b=1,c=0;(这两题需要对照着看一下,看不懂的可以自己打断点,然后debug一下)
原理:i++是先自增,然后返回自己增加之前得值,
++i是先自增。然后返回自己自增之后的值。
13.提到了自动装箱就说一下自动装箱与拆箱
装箱:将基本数据类型转换为包装类的过程
拆箱:将包装类型转换为基本数据类型
14.提到了==就看看==与equals的用法:
==就是判断两个对象的地址是否相同,就是判断两个对象是不是同一个对象,如果是基本数据类型比较的就是值,如果是引用数据类型那比较的就是内存地址。
equals:判断两个对象是否相等。要分两种情况第一终究是类没有重写equals方法,也就是使用的object的equals方法,就和==一样,
第二种就是重写了equals方法,那么就比较的是值。而不是地址。
String A="abc"; String B="abc"; String C=new String("abc"); String D=new String("abc"); System.out.println(A.equals(B)); System.out.println(C.equals(D)); 猜猜答案是什么????true
true
15. 提到了equals就说一下hashcode()(hashCode() 方法用于返回字符串的哈希码。);两个对象的hashCode()相同,则equals()一定为true吗?
不一定,关系是,是两个对象的equals()为true,则hashCode()一定相同,
反过来hashCode()相同,equals()不一定为true。
16.String类使用final修饰,不可以被继承。
17.提到了final就说一下final的作用:
修饰类:该类不能在派生出其他系的子类,不能作为父类被继承,因此一个类不能被同时声明为abstract(抽象类)和final
修饰方法:该方法不能被重写
修饰变量:该变量必须在声明时给定初值,而在以后只能读取不能修改,如果变量是对象,则指的是引用不可修改,但是对象的属性是可以修改的。
18.提到了重写就说一下重载和重写的区别:
重载和重写都是实现多态的方式,重写是运行时的多态性,重载时编译时的多态性
重载:发生在同一个类中,方法名必须相同,参数类型个数不同,顺序不同,方法返回值和访问修饰符可以不同,发生在编译时,一个类中有多个同名的方法,但是具有不同的参数列表
重写:发生在父子类中,方法名,参数列表必须相同,返回值范围小于等于父类(就是返回值类型必须时父类返回值的派生类),抛出异常范围小于等于父类,访问修饰范围大于等于父类,如果父类方法访问修饰符为private则子类不能重写该方法。子类对父类的方法重写,即外壳不变,核心重写,根据子类自己的需要定义自己的特定行为。
19.提到访问修饰符就说一下访问修饰符:
Java中,可以使用访问控制符来保护对类、变量、方法和构造方法的访问。Java 支持 4 种不同的访问权限。
-
default (即默认,什么也不写): 在同一包内可见,不使用任何修饰符。使用对象:类、接口、变量、方法。
-
private : 在同一类内可见。使用对象:变量、方法。 注意:不能修饰类(外部类)
-
public : 对所有类可见。使用对象:类、接口、变量、方法
-
protected : 对同一包内的类和所有子类可见。使用对象:变量、方法。 注意:不能修饰类(外部类)。
我们可以通过以下表来说明访问权限:
修饰符 | 当前类 | 同一包内 | 子孙类(同一包) | 子孙类(不同包) | 其他包 |
---|---|---|---|---|---|
public | Y | Y | Y | Y | Y |
protected | Y | Y | Y | Y/N(说明) | N |
default | Y | Y | Y | N | N |
private | Y | N | N | N | N |
默认访问修饰符-不使用任何关键字
使用默认访问修饰符声明的变量和方法,对同一个包内的类是可见的。接口里的变量都隐式声明为 public static final,而接口里的方法默认情况下访问权限为 public。
如下例所示,变量和方法的声明可以不使用任何修饰符。
实例
String version = "1.5.1";
boolean processOrder() {
return true;
}
私有访问修饰符-private
私有访问修饰符是最严格的访问级别,所以被声明为 private 的方法、变量和构造方法只能被所属类访问,并且类和接口不能声明为 private。
声明为私有访问类型的变量只能通过类中公共的 getter 方法被外部类访问。
Private 访问修饰符的使用主要用来隐藏类的实现细节和保护类的数据。
下面的类使用了私有访问修饰符:
public class Logger { private String format; public String getFormat() { return this.format; } public void setFormat(String format) { this.format = format; } }
实例中,Logger 类中的 format 变量为私有变量,所以其他类不能直接得到和设置该变量的值。为了使其他类能够操作该变量,定义了两个 public 方法:getFormat() (返回 format的值)和 setFormat(String)(设置 format 的值)
公有访问修饰符-public
被声明为 public 的类、方法、构造方法和接口能够被任何其他类访问。
如果几个相互访问的 public 类分布在不同的包中,则需要导入相应 public 类所在的包。由于类的继承性,类所有的公有方法和变量都能被其子类继承。
以下函数使用了公有访问控制:
public static void main(String[] arguments) { // ... }
Java 程序的 main() 方法必须设置成公有的,否则,Java 解释器将不能运行该类。
受保护的访问修饰符-protected
protected 需要从以下两个点来分析说明:
-
子类与基类在同一包中:被声明为 protected 的变量、方法和构造器能被同一个包中的任何其他类访问;
-
子类与基类不在同一包中:那么在子类中,子类实例可以访问其从基类继承而来的 protected 方法,而不能访问基类实例的protected方法。
protected 可以修饰数据成员,构造方法,方法成员,不能修饰类(内部类除外)。
接口及接口的成员变量和成员方法不能声明为 protected。 可以看看下图演示:
子类能访问 protected 修饰符声明的方法和变量,这样就能保护不相关的类使用这些方法和变量。
下面的父类使用了 protected 访问修饰符,子类重写了父类的 openSpeaker() 方法。
class AudioPlayer { protected boolean openSpeaker(Speaker sp) { // 实现细节 } } class StreamingAudioPlayer extends AudioPlayer { protected boolean openSpeaker(Speaker sp) { // 实现细节 } }
如果把 openSpeaker() 方法声明为 private,那么除了 AudioPlayer 之外的类将不能访问该方法。
如果把 openSpeaker() 声明为 public,那么所有的类都能够访问该方法。
如果我们只想让该方法对其所在类的子类可见,则将该方法声明为 protected。
20.提到了访问修饰符就说一下static关键字,被static修饰的叫静态变量,没有被static修饰的叫成员变量
静态变量 | 成员变量 |
存在与方法区中 | 存在于堆内存中 |
静态变量与类共存亡,随着类的加载而存在,随着类的消失而消失。 | 成员变量与对象共存亡,随着对象的创建而存在,随着对象被回收而释放 |
静态变量所属于类,所以也叫类变量 | 成员变量所属于对象,所以也叫实例变量 |
静态变量可以被对象调用,也可以被类名调用 | 成员变量只能被对象所调用 |
21. 思考一下:是否可以从静态方法内部中调用非静态方法?
分两种情况,一种是没有创建对象实例就不可以调用
创建对象实例:可以调用
22.String与StringBuffer StringBuilder的区别。
可变性:String类中使用final关键字字符串数组保存字符串,private final char value[],所以String对象是不可变的,而StringBuffer与StringBuilderd都继承自AbstractStringBuilder ,在AbstractStringBuilder中也是使用字符串数组保存字符串char[]value,但是没有使用final关键字修饰,所以这两种对象都是可变的,大家也可以自行查看源码。
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
/** Cache the hash code for the string */
private int hash; // Default to 0
//String类的部分源码
public final class StringBuffer
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence
{
/**
* A cache of the last value returned by toString. Cleared
* whenever the StringBuffer is modified.
*/
private transient char[] toStringCache;
/** use serialVersionUID from JDK 1.0.2 for interoperability */
static final long serialVersionUID = 3388685877147921107L;
//StringBuffer部分源码
abstract class AbstractStringBuilder implements Appendable, CharSequence {
/**
* The value is used for character storage.
*/
char[] value;
/**
* The count is the number of characters used.
*/
int count;
//AbstractStringBuilder类部分源码
线程安全性:String中的对象是不可变的,也就可以理解为常量,线程安全。 AbstractStringBuilder是StringBuffer与StringBuilder的公共父类,定义了一些字符串的基本操作,如index of(),append等公共方法,但是StringBuffer对方法加了同步锁,或者对调用的方法加了同步锁,所以是线程安全的,下面是StringBuffer的一些方法可以看到是使用synchronized关键字修饰的,StringBuilder没有加锁,所以是不安全的。
@Override
public synchronized int length() {
return count;
}
@Override
public synchronized int capacity() {
return value.length;
}
@Override
public synchronized void ensureCapacity(int minimumCapacity) {
super.ensureCapacity(minimumCapacity);
}
/**
* @since 1.5
*/
@Override
public synchronized void trimToSize() {
super.trimToSize();
}
性能:每次对String类型进行改变的时候,都会生产一个新的String对象,然后指针指向新的String对象,StringBuffer每次都会对StringBuffer对象本身进行操作,而不是生成新的对象,StringBuilder也是这样,但是StringBuilder的性能要高10%-15%,但是却要冒多线程不安全的风险。
总结:
1.操作少量数据 =》String
2.单线程操作字符串缓冲区下操作大量数据=》StringBuilder
3.多线程操作字符缓冲区下操作大量数据=》StringBuffer
19.Object类常见方法总结
在这里说一下native关键字,凡是带了native关键字的,就代表java处理不了了,回去调用底层c语言的库。会进入本地方法栈,调用本地方法接口(JNI)java native interface
JNI作用:扩展java的使用,融合不同的编程语言为java所用。
Object类 | Object类是一个特殊的类,是所有类的父类 |
public final native Class<?>getClass() | native方法,用于返回当前运行时对象的Class对象,使用了final,故不允许子类重写。 |
public int hashCode() | 返回对象的哈希码 |
public boolean equals (Object obj) | 比较两个对象的地址是否相等,String 类对该方法进行了重写,比较两个对象的值是否相等 |
protected native Object clone() throw CloneNotSupportException | 创建并返回当前对象的拷贝。Object本身没有实现Cloneable接口,所以不重写clone方法并调用会出现CloneNotSupportException异常。 |
public String toString() | 返回类的名字@实例的哈希码的16进制的字符串(例如sample.Orc@11b86e7),建议所有子类都重写这个方法。 |
public final native void notify() | native 方法,并且不能重写。唤醒一个在此对象监视器上等待的线程(监视器相当于就是锁的概念)。如果有多个线程在等待只会任意唤醒一个。 |
public final native void notifyAll() | native 方法,并且不能重写。跟 notify 一样,唯一的区别就是会唤醒在 此对象监视器上等待的所有线程,而不是一个线程。 |
public final native void wait(long timeout) throws InterruptedException | native 方法,并且不能重写。暂停线程的执行。注意:sleep 方法没有释 放锁,而 wait 方法释放了锁 。timeout 是等待时间。 |
public final void wait(long time, int nanos) throws InterruptedException | 多了 nanos 参数,这个参数表示额外时间(以毫微秒为单位,范围是 0-999999)。 所以超时的时间还需要加上 nanos 毫秒。 |
.public final void wait() throws InterruptedException | 跟之前的 2 个 wait 方法一样,只不过该方法一直等待,没有超时时间这 个概念。 |
protected void finalize() throws Throwable { } | 实例被垃圾回收器回收的时候触发的操作。 |
23.顺便说一下String 类的常用方法都有那些?
indexOf() | 返回指定字符的索引。 |
charAt() | 返回指定索引处的字符。 |
replace() | 字符串替换。 |
trim() | 去除字符串两端空白 |
split() | 分割字符串,返回一个分割后的字符串数组。 |
getBytes() | 返回字符串的 byte 类型数组。 |
length() | 返回字符串长度。 |
toLowerCase() | 将字符串转成小写字母。 |
toUpperCase() | 将字符串转成大写字符。 |
substring() | 截取字符串。 |
equals() | 字符串比较。比较值是否相等 |
24.提到了clone()就说一下深拷贝和浅拷贝的区别?
数据分为基本数据类型和引用数据类型。基本数据类型:数据直接存储在栈中;引用数据类型:存储在栈中的是对象的引用地址,真实的对象数据存放在堆内存里。
浅拷贝:对于基础数据类型:直接复制数据值;对于引用数据类型:只是复制了对象的引用地址,新旧对象指向同一个内存地址,修改其中一个对象的值,另一个对象的值随之改变。
深拷贝:对于基础数据类型:直接复制数据值;对于引用数据类型:开辟新的内存空间,在新的内存空间里复制一个一模一样的对象,新老对象不共享内存,修改其中一个对象的值,不会影响另一个对象。
深拷贝相比于浅拷贝速度较慢并且花销较大。
25.实现克隆的两种方式
有两种方式:
- 实现Cloneable接口并重写Object类中的clone()方法;
- 实现Serializable接口,通过对象的序列化和反序列化实现克隆,可以实现真正的深度克隆,代码如下:
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public class MyUtil {
private MyUtil() {
throw new AssertionError();
}
@SuppressWarnings("unchecked")
public static <T extends Serializable> T clone(T obj) throws Exception {
ByteArrayOutputStream bout = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bout);
oos.writeObject(obj);
ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bin);
return (T) ois.readObject();
// 说明:调用ByteArrayInputStream或ByteArrayOutputStream对象的close方法没有任何意义
// 这两个基于内存的流只要垃圾回收器清理对象就能够释放资源,这一点不同于对外部资源(如文件流)的释放
}
}
下面是测试代码:
import java.io.Serializable;
/**
* 人类
* @author nnngu
*
*/
class Person implements Serializable {
private static final long serialVersionUID = -9102017020286042305L;
private String name; // 姓名
private int age; // 年龄
private Car car; // 座驾
public Person(String name, int age, Car car) {
this.name = name;
this.age = age;
this.car = car;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Car getCar() {
return car;
}
public void setCar(Car car) {
this.car = car;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + ", car=" + car + "]";
}
}
/**
* 小汽车类
* @author nnngu
*
*/
class Car implements Serializable {
private static final long serialVersionUID = -5713945027627603702L;
private String brand; // 品牌
private int maxSpeed; // 最高时速
public Car(String brand, int maxSpeed) {
this.brand = brand;
this.maxSpeed = maxSpeed;
}
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public int getMaxSpeed() {
return maxSpeed;
}
public void setMaxSpeed(int maxSpeed) {
this.maxSpeed = maxSpeed;
}
@Override
public String toString() {
return "Car [brand=" + brand + ", maxSpeed=" + maxSpeed + "]";
}
}
class CloneTest {
public static void main(String[] args) {
try {
Person p1 = new Person("郭靖", 33, new Car("Benz", 300));
Person p2 = MyUtil.clone(p1); // 深度克隆
p2.getCar().setBrand("BYD");
// 修改克隆的Person对象p2关联的汽车对象的品牌属性
// 原来的Person对象p1关联的汽车不会受到任何影响
// 因为在克隆Person对象时其关联的汽车对象也被克隆了
System.out.println(p1);
} catch (Exception e) {
e.printStackTrace();
}
}
}
注意:基于序列化和反序列化实现的克隆不仅仅是深度克隆,更重要的是通过泛型限定,可以检查出要克隆的对象是否支持序列化,这项检查是编译器完成的,不是在运行时抛出异常,这种是方案明显优于使用Object类的clone方法克隆对象。让问题在编译的时候暴露出来总是好过把问题留到运行时。
26.什么是序列化?
简单说就是为了保存在内存中的各种对象的状态(也就是实例变量,不是方法),并且可以把保存的对象状态再读出来。虽然你可以用你自己的各种各样的方法来保存object states,但是Java给你提供一种应该比你自己好的保存对象状态的机制,那就是序列化。
什么情况下需要序列化:
a)当你想把的内存中的对象状态保存到一个文件中或者数据库中时候;
b)当你想用套接字在网络上传送对象的时候;
c)当你想通过RMI传输对象的时候;
27.什么是反射?
反射是指在运行状态中,对于任意一个类都能够知道这个类所有的属性和方法;并且对于任意一个对象,都能够调用它的任意一个方法;这种动态获取信息以及动态调用对象方法的功能称为反射机制。
28.异或运算的相关性质
异或运算: 相同的两个数异或为0,不同的为1,就可以得出任何一个数和0,异或都等于它本身,任何一个数和它本身异或都等于0,
所以看下边的代码:
a=13;
b=12;
a=a^b;
b=a^b;
a=a^b;
最后的结果是a=12;b=13;
29、new一个对象需要占多少字节?
Object o=new Object();
我们首先来看一下java对象内存布局:
其中对象与数组的内存头是不一样的
在默认开启指针压缩的情况下,Klass Pointer占四个字节,Mark world占8个字节,此时对象的大小不满足8的整数倍,对象填充字节补齐4个字节,此时Object占16个字节。
如果未开启指针压缩,则Klass Pointer占8个字节,对象填充字节为0,还是16个字节。
对象头 | 对象头存储的是对象在运行时状态的相关信息、指向该对象所属类的元数据的指针,如果对象是数组对象那么还会额外存储对象的数组长度 |
实例数据 | 实例数据存储的是对象的真正有效数据,也就是各个属性字段的值,如果在拥有父类的情况下,还会包含父类的字段。字段的存储顺序会受到数据类型长度、以及虚拟机的分配策略的影响 |
对齐填充字节 | 在java对象中,需要对齐填充字节的原因是,64位的jvm中对象的大小被要求向8字节对齐,因此当对象的长度不足8字节的整数倍时,需要在对象中进行填充操作。注意图中对齐填充部分使用了虚线,这是因为填充字节并不是固定存在的部分,这点在后面计算对象大小时具体进行说明 |
所以new一个对象需要占用16个字节。
如有错误请联系!!!!