目录
String、StringBuffer、StringBuilder
Java
基础问题
java中==和equals和hashCode的区别
==
基本数据类型(byte,short,char,int,long,float,double,boolean ) 他们之间的比较应用双等号(==),比较的是他们的值。引用数据类型,当他们用(==)进行比较的时候,比较的是他们在内存中的存放地址
情景1:(s1和s2,指向的是同一个值为1的内存地址,所以s1==s2比较的时候,返回true)
public static void main(String[] args) { String s1="1"; String s2="1"; System.out.println(s1==s2); }
情景2:(s1和s2,分别new出来,开辟了一块新的地址,所以s1==s2比较的时候,是比较地址,返回false)
public static void main(String[] args) { String s1=new String("1"); String s2=new String("1"); System.out.println(s1==s2); }
equals
这里用于引用数据类型和对象的比较,我们知道对象是放在堆中的,栈中存放的是对象的引用(地址)
情景1:通过比较new出来的其他任何对象
public static void main(String[] args) { Person s1=new Person(); Person s2=new Person(); System.out.println(s1.equals(s2)); }
分析源码:任何一个对象都有
equals()方法,
Object定义的是对两个对象的地址值进行的比较(即比较引用是否相同)所以这时候,==和equals是一回事,除非重写equals
情景2:通过比较String引用类型,这里返回true
public static void main(String[] args) { String s1="1"; String s2="1"; System.out.println(s1.equals(s2)); }
分析源码:这里的equals是String类中的equals方法,
像
String 、Math、Integer、Double等这些封装类在使用equals()方法时,已经覆盖了object类的equals()方法,比较的是堆内存地址里面的值。hashcode() 它返回的是一个hashcode值
4种情景:
public static void main(String[] args) { String s1=new String("1"); String s2=new String("1"); System.out.println(s1==s2); System.out.println(s1.hashCode()+" "+s2.hashCode()); }
总结:
1、如果两个对象equals,Java运行时环境会认为他们的hashcode一定相等。 2、如果两个对象不equals,他们的hashcode有可能相等。 3、如果两个对象hashcode相等,他们不一定equals。 4、如果两个对象hashcode不相等,他们一定不equals。
Serializable 和Parcelable 的区别
序列化,表示将一个对象转换成可存储或可传输的状态
Serializable Java 序列化接口 在硬盘上读写 读写过程中有大量临时变量的生成,
内部执行大量的i/o操作,效率很低。
Seralizable无法序列化静态变量,当一个父类实现序列化,子类自动实现序列化,不需要再显示实现Serializable接口
只需对需要序列化的类class执行就可以,不需要手动去处理序列化和反序列化的过程
Parcelable Android 序列化接口 效率高 使用麻烦 在内存中读写 writeToParcel:用来序列化数据
对象不能保存到磁盘中,继承该接口,需要实现两个方法
@Override
public int describeContents() {return 0; }
@Override
public void writeToParcel(Parcel dest, int flags) { }
public static final Creator<Person> CREATOR = new Creator<Person>() {
@Override
public PersoncreateFromParcel(Parcel in) { return new Person(in); } //从序列化后的对象中创建原始对象
@Override
public Person[] newArray(int size) { return new Person[size]; }//创建指定长度的原始对象数组
};
***************************************************************************
writeToParcel:用来序列化数据
describeContents:描述 只针对一些特殊的需要描述信息的对象,需要返回1,其他情况返回0就可以
CREATOR :用来反序列化
举个例子,来演示一下 Parcelable的三大过程介绍(序列化,反序列化,描述)
public class People implements Parcelable { private String name; private List<String>list; public String getName() { return name; } public void setName(String name) { this.name = name; } public List<String> getList() { return list; } public void setList(List<String> list) { this.list = list; } protected People(Parcel in) { name = in.readString(); list = in.createStringArrayList(); } //反序列化 public static final Creator<People> CREATOR = new Creator<People>() { @Override public People createFromParcel(Parcel in) { return new People(in);//从序列化后的对象中创建原始对象 } @Override public People[] newArray(int size) { return new People[size];//创建指定长度的原始对象数组 } }; @Override public int describeContents() { return 0; } //序列化 @Override public void writeToParcel(Parcel dest, int flags) { dest.writeString(name); dest.writeStringList(list); } }
final,finally,finalize的区别
final修饰符
被final修饰的类不能被继承;
被final修饰的变量,赋值之后,不可以改变值;
被final修饰的方法,那么继承该类的子类,可以重载,不可以重写(覆盖),
前提是不能是private修饰,因为private的属性或方法对子类来说不能够继承。
finally是与try catch同时使用的,绝大多数情况一定执行的语句块,也有特殊情况,比如你在try中结束程序 finalize
finalize()是Object的方法,子类可以覆盖这个方法来做一些系统资源的释放或者数据的清理
当对象不再被任何对象引用时,GC会调用该对象的finalize()方法,
由于GC的自动回收机制,因而并不能保证finalize方法会被及时执行,也不能保证它一定会被执行
可以重写,但是尽量避免重写,重写finalize()方法意味着延长了回收对象时需要进行更多的操作,从而延长了对象回收的时间
String、StringBuffer、StringBuilder
string是字符串常量不可变的,String对象创建之后,就会在堆内存中占据一块固定的空间,它是不变的,
但是我们又可以对String对象重新赋值或者拼接,这时候,其实是改变了String对象的指向,指向新的字符串的地址
原来的对象依然在内存中,JVM会在合适的时间回收,但是也会造成极大的资源浪费
StringBuffer用来字符串拼接,不会产生新的对象,线程安全,效率低 StringBuilder用来字符串拼接,不会产生新的对象,线程不安全,效率高
父类的静态方法能否被子类继承,重载和重写?
通过代码可以看到
1,子类可以继承父类同名静态方法,并且可以重载;
2,父类类型引用子类对象,这里的b调用的是父类的method1()方法,也就是子类无法重写父类静态方法;
使用static修饰的静态方法会随着类的定义而被分配和装载入内存中,所以其实静态方法从程序开始运行后就已经分配了内存,子类中如果定义了相同名称的静态方法,并不会重写,而应该是在内存中又分配了一块给子类的静态方法,所以不回重写
四种内部类
- 将一个类定义在另一个类里面或者一个方法里面,这样的类称为内部类
成员内部类
成员内部类可以无条件访问外部类的所有成员属性和成员方法(包括private成员和静态成员);
成员内部类可以使用权限修饰符修饰 如果在内部类中也有和外部类有同名属性,那么此时调用的是内部类,那如何区分呢?
外部类.this.成员变量
外部类.this.成员方法
在成员内部类中,不能定义静态变量,但是可以定义final静态常量;那么为什么呢?
java类加载顺序:
首先加载类,执行static变量初始化,接下来执行对象的创建,
如果我们要执行代码中的变量int x1 初始化,那么必须先执行加载外部类,再加载内部类,
最后初始化静态变量 x1 ,问题就出在加载内部类上面,我们可以把内部类看成外部类的非静态成员,
它的初始化必须在外部类对象创建后以后进行,要加载内部类必须在实例化外部类之后完成 ,
但是java虚拟机要求所有的静态变量必须在对象创建之前完成,这样便产生了矛盾。
而java的final常量放在内存中常量池,它的机制与变量是不同,编译时加载常量不需要加载类,所以没有矛盾。
所以:
final static int x1=100; ✔ 编译时常量,可以赋值
final static int x1=x+y; ✘ 运行时常量,不可以赋值
public class InnerTest { private int x = 0; private static int y = 0; // private Inner inner = null; // public Inner getInnerInstance() { // if(inner==null) { // inner=new Inner(); // } // return inner; // } public void show() { System.out.println("外部类"); } class Inner { private int x=1; final static int x1=100; public void showMessage() { System.out.println("成员内部类调用內部私有成员x= " + x); System.out.println("成员内部类调用內部私有成员x1= " + x); System.out.println("成员内部类调用外部同名私有成员InnerTest.this.x "+InnerTest.this.x); System.out.println("成员内部类调用外部静态成员y " + y); show(); } } }
外部类中如果要访问成员内部类的成员,必须先创建一个成员内部类的对象,再通过指向这个对象的引用来访问
public void show() { System.out.println("外部类"); Inner inner=new Inner(); System.out.println("外部类调用内部类成员:"+inner.x); inner.showMessage(); }
访问入口(方式1)
public class Test { public static void main(String[] args) { InnerTest in = new InnerTest(); InnerTest.Inner inner = in.new Inner(); inner.showMessage(); } }
访问入口(方式2)
public class Test { public static void main(String[] args) { InnerTest in = new InnerTest(); InnerTest.Inner inner = in.getInnerInstance(); inner.showMessage(); } }
局部内部类
局部内部类是定义在一个方法或者一个作用域里面的类;
它和成员内部类的区别在于局部内部类的访问仅限于方法内或者该作用域内。
局部内部类和局部变量一样,不能使用访问控制修饰符 局部内部类中不能定义 static 成员 在局部内部类中可以访问外部类的所有成员,可以访问方法中的任何类型的局部成员; int x1=10;这里必须赋值,也就意味着局部内部类只能访问方法中的final类型的数据
究其根本原因,是局部内部类对象的生命周期比局部变量的生命周期长,当外部调用InnerTest中的show方法,
show方法执行完之后,方法里面定义的所有变量(隐式final类型的x1 和 显式final类型的x2)都被回收了,
是此时内部类的对象还可以访问x1或者x2这是矛盾的。
局部变量设置为final,对它初始化后,我就不让你再去修改这个变量,
就保证了内部类的成员变量和方法的局部变量的一致性。这实际上也是一种妥协。
public class InnerTest { private int x = 0; private static int y = 0; public void show() { int x1=10; final int x2=(int) Math.random(); class Inner { int y1 = x2; public void showMessage() { System.out.println("局部内部类调用方法局部成员x1= " + x1); System.out.println("局部内部类调用方法局部成员x= " + x2); System.out.println("成员内部类调用外部同名私有成员InnerTest.this.x " + InnerTest.this.x); System.out.println("成员内部类调用外部静态成员y " + y); } } Inner i = new Inner(); i.showMessage(); } }
静态内部类
在创建静态内部类的实例时,只需要直接调用 静态内部类中可以定义静态成员和普通成员 外部类可以通过创建静态内部类实例的方法来调用静态内部类的非静态属性和方法
外部类可以直接通过“ 外部类.内部类.属性(方法)” 的方式直接调用静态内部类中的静态属性和方法
静态内部类可以直接调用外部类的静态属性和方法
静态内部类可以通过创建外部类实例的方法调用外部类的非静态属性和方法
public class InnerTest { private int x = 0; private static int y = 0; static class Inner { int x=1; static int x2=2; final int x3=3; public void showMessage() { InnerTest innerTest=new InnerTest(); System.out.println("成员内部类调用外部同名私有成员x " + innerTest.x); System.out.println("成员内部类调用外部静态成员y " + y); } } }
public static void main(String[] args) { InnerTest.Inner in = new InnerTest.Inner(); in.showMessage(); }
匿名内部类
匿名类是指没有类名的内部类,必须在创建时使用 new 语句来声明类,更简单
比如
new Thread() {
public void run() {
System.out.println("Thread running!");
};
{
start();
}
};
如果匿名类位于一个方法中,则匿名类只能访问方法中 final 类型的局部变量和参数
比如安卓中的 new OnClickListener()
匿名类和局部内部类一样,可以访问外部类的所有成员 匿名类中允许使用非静态代码块进行成员初始化操作 匿名类的非静态代码块会在父类的构造方法之后被执行 不能定义构造方法,匿名内部类没有类名,无法定义构造方法,
但是,匿名内部类拥有与父类相同的所有构造方法
匿名内部类必须继承一个父类或者实现一个父接口。
new <类或接口>() { // 类的主体 };
public interface Click2 { void onClick(); }
public class Test { public static void main(String[] args) { int a = 10; Click2 click2=new Click2() { @Override public void onClick() { System.out.println(a); } }; click2.onClick(); } }
这里的a是不可再重新赋值的,final保持数据的一致性,从 Java 8 开始,我们可以不加 final 修饰符,由系统默认添加,当然这在 Java 8 以前的版本是不允许的。Java 将这个功能称为 Effectively final 功能
四种引用
强引用
声明格式:Object obj = new Object()
- Java中默认声明的都是强引用。
- 调用的时候,直接访问目标对象obj。
- 强引用所指向的对象在任何时候都不会被系统回收。JVM宁愿抛出OOM异常,也不会回收强引用所指向的对象。
- 强引用可能导致内存泄漏。如果想中断强引用与对象之间的联系,可以显示的将强引用赋值为null,这样一来,JVM就可以适时的回收对象了,例如我们在安卓开发过程中,onDestroy()的声明周期方法中,把一些变量设置为null,来防止内存不足导致的崩溃;
软引用
声明格式以及获取方式,通过get获取
SoftReference<String> sr = new SoftReference<String>(new String("hello world"));
System.out.println(sr.get());
- 在内存足够的时候,软引用对象不会被回收,只有在内存不足时,系统则会回收软引用对象,如果回收了软引用对象之后仍然没有足够的内存,才会抛出内存溢出异常;
- 这种特性常常被用来实现缓存技术,比如网页缓存,图片缓存
- SoftReference的特点是它的一个实例保存对一个Java对象的软引用, 该软引用的存在不妨碍垃圾收集线程对该Java对象的回收。
弱引用
声明:
WeakReference<String> sr = new WeakReference<String>(new String("hello world"));
System.out.println(sr.get());
- 弱引用也是用来描述非必需对象的,当JVM进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象。
- 在Android中,常常使用弱引用来解决内存泄漏的问题,例如handler持有了一个Activity的引用,当这个Activity退出时消息队列中还有未处理的消息或者正在处理消息,而消息队列中的Message持有mHandler实例的引用,mHandler又持有Activity的引用,所以导致该Activity的内存资源无法及时回收,引发内存泄漏。解决办法是让handler持有一个activity的弱引用。
曾经遇到过两个面完问题
- 如果我打开了一个activity,在网络请求的过程中,关闭activity会怎么样?
- 如果在activity中,使用hanler开启了子线程,(子线程未结束)关闭activity,会怎么样?
网络请求都是异步处理的,也就是在子线程中处理的,他会引用当前的activity,
如果关闭activity,就导致该Activity无法被回收(即内存泄露),因为它是强引用
虚引用
声明:
ReferenceQueue<Object> queue = new ReferenceQueue<Object>();
PhantomReference<Object> phantom = new PhantomReference<Object>(new Object(), queue);
- 虚引用和前面的软引用、弱引用不同,它并不影响对象的生命周期。
- 如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。
- 虚引用必须和引用队列(ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。