| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Name | Name | Last commit date | ||
|---|---|---|---|---|
java学习总结
JAVA基础知识
1. final关键字
Ø 修饰类,代表该类不能被继承;final类比普通类有更高的效率
Ø 修饰方法,代表该方法不能被子类重写;private方法会都隐式的指定为final的;final方法比非final方法要快,因为在编译的时候已经静态绑定了,不需要在运行时在动态绑定;
Ø 修饰变量:如果是基本数据类型,则一旦被初始化,值不能更改;如果是引用类型,一旦被初始化,不能再指向另外的对象,但是指向对象的内容是可以变的;换句话说,如果一个变量或方法参数被final修饰,就表示它只能被赋值一次
Ø final成员变量必须在声明的时候或者构造器中进行初始化;
Ø final修饰局部变量:系统不会为局部变量进行初始化,局部变量必须有程序员显示初始化。因此使用final修饰局部变量时,既可以在定义时指定默认值(后面的代码不能再对变量赋值),也可以不指定默认值,而在后面的代码中对final变量赋初值(仅一次)
Ø final变量在编译期就能知道确定的值,编译器会将其作为编译期常量使用,类似于C语言的宏替换;
2. static关键字
Ø static修饰的成员变量和方法,从属于类,方便在没有创建对象的情况下进行调用(方法/变量)。static方法是属于类的,非实例对象,在JVM加载类时,就已经存在内存中,不会被虚拟机GC回收掉,这样内存负荷会很大,而非static方法会在运行完毕后被虚拟机GC掉,减轻内存压力
Ø 虽然在静态方法中不能访问非静态成员方法和非静态成员变量,但是在非静态成员方法中是可以访问静态成员方法和静态成员变量。
Ø 和静态变量、静态方法一样,静态块里面的代码只执行一次,且只在初始化类的时候执行
Ø static修饰类:这个用得相对比前面的用法少多了,static一般情况下来说是不可以修饰类的,如果static要修饰一个类,说明这个类是一个静态内部类(注意static只能修饰一个内部类),也就是匿名内部类。
Ø 静态资源的加载顺序是严格按照静态资源的定义顺序来加载的
Ø 静态代码块对于定义在它之后的静态变量,可以赋值,但是不能访问。
Ø 静态代码块是严格按照父类静态代码块->子类静态代码块的顺序加载的,且只加载一次。
Ø 静态资源属于类,但是是独立于类存在的。从JVM的类加载机制的角度讲,静态资源是类初始化的时候加载的,而非静态资源是类new的时候加载的。类的初始化早于类的new,比如Class.forName(“xxx”)方法,就是初始化了一个类,但是并没有new它,只是加载这个类的静态资源罢了。所以对于静态资源来说,它是不可能知道一个类中有哪些非静态资源的;但是对于非静态资源来说就不一样了,由于它是new出来之后产生的,因此属于类的这些东西它都能认识。所以上面的几个问题答案就很明确了:
1、静态方法能不能引用非静态资源?因为静态方法是随着类的加载而加载的,静态是先于对象存在的,你要访问非静态的东西,可是这时候可能它还不存在。
2、静态方法里面能不能引用静态资源?可以,因为都是类初始化的时候加载的,大家相互都认识。
3、非静态方法里面能不能引用静态资源?可以,非静态方法就是实例方法,那是new之后才产生的,那么属于类的内容它都认识。
3. 解释下重写(override)和重载(overload)的区别?
Ø 重写是子类对父类允许访问的方法的重新编写,方法名、参数列表、返回值必须相同(外壳不变,核心重写);子类根据需要重写父类的方法,定义自己的行为,是子类和父类多态性的一种体现,在运行期进行动态绑定
Ø 重载是在一个类里面,方法名字相同,但是参数列表不相同,参数个数不同或数量相同而类型和顺序不同(注意多种参数类型次序不同也是重载);被重载的方法必须改变参数列表
Ø private和final的方法不能被重写,static的方法不能被重写,但是可以重新声明
Ø 重写方法不能抛出新的检查异常或者比被重写方法申明的更加宽泛的异常。例如: 父类的一个方法申明了一个检查异常 IOException,但是在重写这个方法的时候不能抛出 Exception 异常,因为 Exception 是 IOException 的父类,只能抛出 IOException 的子类异常。
Ø 重写方法访问权限不能比父类重写的方法的访问权限更低。例如:如果父类的一个方法被声明为 public,那么在子类中重写该方法就不能声明为 protected。
Ø 重写方法返回类型与被重写方法可以不同,但是必须是父类返回值的派生类(java5 及更早版本返回类型要一样,java7 及更高版本可以不同)。
4. 抽象类和接口的区别?接口定义的变量一定是常量吗?接口中可以定义函数实现吗?抽象类一定要有抽象函数吗?抽象类可以有构造函数吗?抽象类能否用static声明?抽象类可以定义static方法并直接调用吗?抽象类可以用final修饰吗?
Ø 抽象类和抽象方法都使用 abstract 关键字进行声明。如果一个类中包含抽象方法,那么这个类必须声明为抽象类。抽象类和普通类最大的区别是,抽象类不能被实例化,只能被继承。
Ø 接口是抽象类的延伸,在 Java 8 之前,它可以看成是一个完全抽象的类,也就是说它不能有任何的方法实现。从 Java 8 开始,接口也可以拥有默认的方法实现,这是因为不支持默认方法的接口的维护成本太高了。在 Java 8 之前,如果一个接口想要添加新的方法,那么要修改所有实现了该接口的类,让它们都实现新增的方法。接口的成员(字段 + 方法)默认都是 public 的,并且不允许定义为 private 或者 protected。从 Java 9 开始,允许将方法定义为 private,这样就能定义某些复用的代码又不会把方法暴露出去。来自 <https://github.com/CyC2018/CS-Notes/blob/master/notes/Java%20%E5%9F%BA%E7%A1%80.md>
Ø 在很多情况下,接口优先于抽象类。因为接口没有抽象类严格的类层次结构要求,可以灵活地为一个类添加行为。并且从 Java 8 开始,接口也可以有默认的方法实现,使得修改接口的成本也变的很低。
区别:
Ø 抽象类可以有构造方法,接口中不能有构造方法。
Ø 从设计层面上看,抽象类提供了一种 IS-A 关系,需要满足里式替换原则,即子类对象必须能够替换掉所有父类对象。而接口更像是一种 LIKE-A 关系,它只是提供一种方法实现契约,并不要求接口和实现接口的类具有 IS-A 关系。
Ø 从使用上来看,一个类可以实现多个接口,但是不能继承多个抽象类。
Ø 接口的字段只能是 static 和 final 类型的,而抽象类的字段没有这种限制,可以拥有普通成员变量。
Ø 接口的成员只能是 public 的,而抽象类的成员可以有多种访问权限。
Ø 接口中的属性必然是常量,只能读不能改,这样才能为实现接口的对象提供一个统一的属性。通俗的讲,你认为是要变化的东西,就放在你自己的实现中,不能放在接口中去,接口只是对一类事物的属性和行为更高层次的抽象。对修改关闭,对扩展(不同的实现implements)开放,接口是对开闭原则的一种体现。
举例来说,如果接口A中有一个public访问权限的静态变量a。按照java的语义, 我们可以不通过实现接口的对象来访问变量a,通过A.a = xxx;就可以改变接口中的变量a的值了。正如抽象类中是可以这样做的,那么实现接口A的所有对象也都会自动拥有这一改变后的a的值了,也就是说一个地方改变了a,所有这些对象中a的值也都跟着变了。这和抽象类有什么区别呢,怎么体现接口更高的抽象级别呢,怎么体现接口提供的统一的协议呢,那还要接口这种抽象来做什么呢?所以接口中不能出现变量,如果有变量,就和接口提供的统一的抽象这种思想是抵触的。
Ø java8后,接口中可以定义函数实现。在接口中可以定义实现的方法体是java8的一大特性,可以定义多个静态或者默认的方法,静态必须加上static,默认方法必须加上default关键字。静态方法可以直接使用接口名称调用,因default方法非静态方法,且接口是不能创建对象的,故只能通过子类来实现接口。通过子类调用时,如果子类实现了默认方法,调用子类的方法,否则调用接口类的方法
Ø 抽象类可以没有抽象方法,但是如果你的一个类已经声明成了抽象类,即使这个类中没有抽象方法,它也不能再实例化,即不能直接构造一个该类的对象。如果一个类中有了一个抽象方法,那么这个类必须声明为抽象类,否则编译通不过。
普通类是一个完善的功能类,可以直接产生实例化对象,并且在普通类中可以包含有构造方法、普通方法、static方法、常量和变量等内容。而抽象类是指在普通类的结构里面增加抽象方法的组成部分。抽象方法,是指没有方法体的方法,同时抽象方法还必须使用关键字abstract做修饰。拥有抽象方法的类就是抽象类,抽象类要使用abstract关键字声明。
Ø 由于抽象类里会存在一些属性,那么抽象类中一定存在构造方法,其存在目的是为了属性的初始化。并且子类对象实例化的时候,依然满足先执行父类构造,再执行子类构造的顺序。
Ø 外部抽象类不允许使用static声明,而内部的抽象类运行使用static声明。使用static声明的内部抽象类相当于一个外部抽象类,继承的时候使用“外部类.内部类”的形式表示类名称
Ø 抽象类可以定义static方法。任何时候,如果要执行类中的static方法的时候,都可以在没有对象的情况下直接调用,对于抽象类也一样。
Ø 抽象类无法进行实例化操作(为什么不能直接实例化呢?当一个类实例化之后,就意味着这个对象可以调用类中的属性或者方法了,但在抽象类里存在抽象方法,而抽象方法没有方法体,没有方法体就无法进行调用。既然无法进行方法调用的话,又怎么去产生实例化对象呢)。因为抽象类必须有子类继承,而final定义的类不能被继承,所以不能用final修饰
5. 值传递和引用传递
Ø java中的数据类型分为两类:基本类型和引用类型。基本类型保存原始值,代表的数值本身,引用类型保存对象的引用,及对象在内存空间的地址,而不是对象本身。
Ø 基本类型包括:byte、short、int、long、float、boolean、char、double;引用类型包括:类类型、接口类型、数组
Ø 基本数据类型在引用时,系统就会给它分配空间;引用类型在声明时,只分配了引用空间,而不分配数据空间。注意引用也是占用空间的,一个空Object对象的引用大小大概是4byte
Ø 值传递:方法调用时,实际参数将它的值传递给形式参数,函数接收的是原始值的一个copy,对形参的修改不会影响实参
Ø 引用传递:也称为传地址。形参接收的是原始值的地址,形参和实参指向同一个内存地址(同一对象),所以对形参的修改会影响到实际的对象
Ø String,Integer和Double等几个基本类型的包装类,他们都immutable类型,没有办法提供修改自身的函数,每次操作都是新生成一个对象,可以理解为值传递
6. 浅拷贝和深拷贝
Ø 引用拷贝:两个对象的地址值是相同的,两者指向的是同一个对象,如 Object obj = new Object(); Object obj1 = obj;
Ø 对象拷贝:拷贝对象和被拷贝对象的地址是不同的,拷贝的时候创建了新的对象,而不是将原对象的地址赋给了新的引用对象。如: Teacher teacher2 = teacher.clone(); 深拷贝和浅拷贝都是对象拷贝
Ø 浅拷贝:只复制对象包含的值类型的成员变量,而引用类型的成员对象没有被复制。被复制对象的所有变量都和原来的对象有相同的值,而所有的对其他对象的引用仍然指向原来的对象。即对象的浅拷贝会对“主”对象进行拷贝,但不会复制主对象里面的对象。引用的对象会在原来的对象和它的副本之间共享。
Ø 深拷贝:除了对象本身被复制外,对象所包含的所有成员变量都会被复制,包括引用类型的成员对象。深拷贝相比于浅拷贝速度较慢并且花销较大
7. 不可变类(immutable)和String的不可变性
Ø 可变类:相对于不可变类,可变类创建实例后可以改变其成员变量值,开发中创建的大部分类都属于可变类。
Ø 不可变类:所谓的不可变类是指这个类的实例一旦创建完成后,就不能改变其成员变量值。这种特性使得不可变类提供了线程安全的特性但同时也带来了对象创建的开销,每更改一个属性都是重新创建一个新的对象。如JDK内部自带的很多不可变类:Interger、Long和String等
Ø 不可变类的设计方法:
1. 类添加final修饰符,保证类不被继承。
如果类可以被继承会破坏类的不可变性机制,只要继承类覆盖父类的方法并且继承类可以改变成员变量值,那么一旦子类以父类的形式出现时,不能保证当前类是否可变。
2. 保证所有成员变量必须私有,并且加上final修饰
通过这种方式保证成员变量不可改变。但只做到这一步还不够,因为如果是对象成员变量有可能再外部改变其值。所以第4点弥补这个不足。
3. 不提供改变成员变量的方法,包括setter
避免通过其他接口改变成员变量的值,破坏不可变特性。
4.通过构造器初始化所有成员,进行深拷贝(deep copy)
如果构造器传入的对象直接赋值给成员变量,还是可以通过对传入对象的修改进而导致改变内部变量的值。
5. 在getter方法中,不要直接返回对象本身,而是克隆对象,并返回对象的拷贝
这种做法也是防止对象外泄,防止通过getter获得内部可变成员对象后对成员变量直接操作,导致成员变量发生改变。
https://www.cnblogs.com/jaylon/p/5721571.html
Ø Sting的不可变性:
String类被final修饰,不可继承
string内部所有成员都设置为私有变量
不存在value的setter
并将value和offset设置为final。
当传入可变数组value[]时,进行copy而不是直接将value[]复制给内部变量.
获取value时不是直接返回对象引用,而是返回对象的copy.
Ø String对象的不可变性的优缺点:
1.字符串常量池的需要.
字符串常量池可以将一些字符常量放在常量池中重复使用,避免每次都重新创建相同的对象、节省存储空间。但如果字符串是可变的,此时相同内容的String还指向常量池的同一个内存空间,当某个变量改变了该内存的值时,其他遍历的值也会发生改变。所以不符合常量池设计的初衷。
2. 线程安全考虑。
同一个字符串实例可以被多个线程共享。这样便不用因为线程安全问题而使用同步。字符串自己便是线程安全的。
3. 类加载器要用到字符串,不可变性提供了安全性,以便正确的类被加载。譬如你想加载java.sql.Connection类,而这个值被改成了myhacked.Connection,那么会对你的数据库造成不可知的破坏。
4. 支持hash映射和缓存。
因为字符串是不可变的,所以在它创建的时候hashcode就被缓存了,不需要重新计算。这就使得字符串很适合作为Map中的键,字符串的处理速度要快过其它的键对象。这就是HashMap中的键往往都使用字符串。
缺点:如果有对String对象值改变的需求,那么会创建大量的String对象。
Ø 虽然String对象将value设置为final,并且还通过各种机制保证其成员变量不可改变。但是还是可以通过反射机制的手段改变其值
8. 如何实现一个list类型的深拷贝?Java的clone接口的作用是什么?
Ø 在Java语言中,如果需要实现深克隆,可以通过覆盖Object类的clone()方法实现,也可以通过序列化(Serialization)等方式来实现。:
1、先序列化对象,再反序列化对象
public static <T> List<T> deepCopyBySerializable(List<T> src)
throws IOException, ClassNotFoundException
{
ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteOut);
objectOutputStream.writeObject(src);
ByteArrayInputStream byteIn = new ByteArrayInputStream(byteOut.toByteArray());
ObjectInputStream objectInputStream = new ObjectInputStream(byteIn);
return (List<T>)objectInputStream.readObject();
}
2、使用clone方法:在对对象调用clone()方法完成复制后,接着对对象中的非基本类型的属性也调用clone()方法完成深复制。这里示例的Person类必须实现Cloneable接口public static List<Person> deepCopyByClone(List<Person> src) {
List<Person> dest = new ArrayList<>();
src.stream().forEach(member -> dest.add((Person)member.clone()));
return dest;
}
Person 对象示例
@Data
@AllArgsConstructor
static class Person implements Serializable, Cloneable {
private static final long serialVersionUID = 872390113109L; //最好是显式声明ID
private String name;
private int age;
@Override
public Object clone() {
Person o = null;
try {
o = (Person) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return o;
}
}
Ø Java中的所有类默认都继承自Object类,而Object类中提供了一个clone()方法。这个方法的作用是返回一个Object对象的复制。这个复制函数返回的是一个新的对象而不是一个引用。以下是使用clone()方法的步骤:
• 被复制的类需要实现Clonenable接口(不实现的话在调用clone方法会抛出CloneNotSupportedException异常), 该接口为标记接口(不含任何方法)
• 在类中重写Object类中的clone()方法,访问修饰符设为public。方法中调用super.clone()方法得到需要的复制对象。(native为本地方法)。无论clone类的继承结构是什么,super.clone()都会直接或间接调用java.lang,Object类的clone()方法。
• 把浅复制的引用指向原型对象新的克隆体。
Ø 在C++语言中,当开发人员自定义复制构造函数时,会存在浅复制与深复制之分。Java语言在重载clone()方法时也存在同样的问题,当类中只有一些基本的数据类型时,采用上述方法就可以了,但是当类中包含了一些对象时,就需要用到深复制了,实现方法是在对对象调用clone()方法完成复制后,接着对对象中的非基本类型的属性也调用clone()方法完
成深复制。那么在编程时,如何选择使用哪种复制方式呢?首先,检查类有无非基本类型(即对象)的数据成员。若没有,则返回super.clone()即可;若有,确保类中包含的所有的非基本类型的成员变量都实现了深复制。
9. String是一个基本类型么?java的基本类型有哪些?String和StringBuffer和StringBuilder的区别是什么?
Ø String不是基本数据类型。基本类型有float、double、byte、short、int、long、char、boolean。
Ø String类是不可变类,任何对String的改变都会引发新的String对象的生成;StringBuffer和StringBuilder则是可变类,任何对它所指代的字符串的改变都不会产生新的对象(底层都是char[])。两者非常类似,均代表可变的字符序列,而且方法也一样。
Ø StringBufferd支持并发操作,线程安全的,适合多线程中使用(使用synchronized实现)。StringBuilder不支持并发操作,线程不安全的,不适合多线程中使用。类似HashTable和HashMap的区别
Ø StringBuilder 相较于 StringBuffer 有速度优势,所以多数情况下建议使用 StringBuilder 类。然而在应用程序要求线程安全的情况下,则必须使用 StringBuffer 类。
Ø string使用陷阱:String s = "a"; // 创建了一个字符串
s = s + "b"; // 实际上原来的"a"字符串对象已经丢弃了,现在又产生了一个字符串s + "b"(也就是”ab,').如果多次执行这些改变串内容的操作,会导致大量副本字符串对象存留在内存中,降低效率。如果这样的操作放到循环中,会极大影响程序的性能.
10. String str = 'i' 与 String str = new String('i')一样吗?不同点在哪里?String.intern()方法?String str = new String('xyz')创建了几个对象
Ø 不一样。因为内存的分配方式不一样。String str = "i"的方式,java虚拟机会将其分配到常量池中;而String str = new String("i")的方式,则会被分配到堆内存中。
Ø jdk1.8后,移除了永久代,方法区的实现改为了元空间,但是常量池还在堆中。注意,常量池不会重复创建对象,在String str1="i"中,把i值存在常量池,地址赋给str1。假设再写一个String str2=“i”,则会把i的地址赋给str2,但是i对象不会重新创建,他们引用的是同一个地址值,共享同一个i内存。而堆内存会创建新的对象,假设再写一个String str3=new String(“i”),则会创建一个新的i对象,然后将新对象的地址值赋给str3。虽然str3和str1的值相同但是地址值不同。
Ø 拓展:堆内存用来存放由new创建的对象和数组。 在堆中分配的内存,由Java虚拟机的自动垃圾回收器来管理。常量池指的是在编译期被确定,并被保存在已编译的.class文件中的一些数据。
Ø String.intern()是一个native方法,调用str.intern()方法时,返回的是字符串对象在常量池中和它相等的字符串的引用。JVM首先会在当前类的常量池中查找是否存在于str等值的String,若存在则直接返回常量池中相应的String的引用;若不存在,则会在常量池中创建一个等值的String,然后返回这个String在常量池中的引用。jdk1.7后,如果在常量池找不到对应的字符串,则不会再将字符串拷贝到常量池,而是在常量池生产一个对原字符串的引用,也就是把堆上的地址复制到常量池。因此,只要是等值的String对象,使用intern方法返回的都是常量池中同一个String引用。Jdk6 中常量池位于PremGen区,大小受限,不建议使用String.intern()方法,不过Jdk7 将常量池移到了Java堆区,大小可控,可以通过-xx:StringTableSize参数设置StringTable的大小,可以重新考虑使用String.intern()方法,但是由对比测试可知,使用该方法的耗时不容忽视,所以需要慎重考虑该方法的使用;String.intern()方法主要适用于程序中需要保存有限个会被反复使用的值的场景,这样可以减少内存消耗,同时在进行比较操作时减少时耗,提高程序性能。
Ø 首先要看常量池里是否有“xyz”这个字符串,如果有(String str = "xyz";出现时则有),则创建一个,如果没有,则创建两个(一个在常量池,一个在堆中)。
| Back | FazBrowse Home | New Git URL |