boolean只有两个值:true、false,可以使用1bit来存储,但是具体的大小没有明确规定。JVM会在编译时期将boolean类型的数据转换为int,使用1来表示true,0表示flase。JVM支持boolean数组,但是是通过读写byte数组来实现的。
###包装类型
基本数据类型都有对应的包装类型,基本数据类型与其对应的包装类型之间的赋值使用自动装箱与拆箱完成。
interger x = 2;//装箱 调用了Interger。valueOf(2)
int y = x; //拆箱 调用了X.intVlaue();
###缓存池
new interger(123)与Inerger.valueOf(123)的区别在于:
Integer x=new Integer(123);
Integer y=new Integer(123);
System.out.println(x==y);//false
Integer z = Integer.valueOf(123);
Integer k = Integer.valueOf(123);
System.out.println(z==k);//true
valueOf方法的实现比较简单,就是先判断值是否在缓存池中,如果在的话就直接返回缓存池的内容。
public static Integer valueOf(int i){
if(i>=InergerCache.low&&i<=InergerCache.high)
return IntergeCache.cache[i+ (-InergerCache.low)];
return new interger(i);
}
在java8中,Interger缓存池的大小默认为-128~127
static final int low = -128;
static final int high;
static final interger cache[];
static {
//high value may be configured by property
int h=127;
String intergerCacheHighProValue =
sun.misc.VM.getSavedPropert("java.lang.Integer.IntegerCache.high");
if(intergerCacheHighProValue!=null){
try {
int i = parseInt(intergerCacheHighProValue);
i =Math.max(i,127);
//Maximum array size is Integer.MAX_VALUE
h = Math.min(i,Interger.MAX_VALUE - (-low)-1);
} catch(NumberFormatException nfe){
//// If the property cannot be parsed into an int, ignore it.
}
}
high = h;
cache = new Interger[(high -low) +1];
int j = low;
for(int k = 0;k<cahe.length;k++)
cache[k] = new Interger(j++);
//range [-128,127] must be interned (JLS7 5.1.7)
assert InergerCache.high >=127;
}
编译器会在自动装箱过程中调用valueOf()过程中调用,因此多个值相同且值在缓存池范围内的 Integer 实例使用自动装箱来创建,那么就会引用相同的对象
Interger m = 123;
Interger n = 123;
System.out.println(m==n);//true
基本类型对应的缓冲池如下
在使用这些基本类型对应的包装类型时,如果该数值范围在缓冲池范围内,就可以直接使用缓冲池中的对象。
在jdk 1.8所有的数值类缓冲池中,Interger的缓冲池IntergerCache很特殊,这个缓冲池的下界是-128,上界默认是127,但是这个上界是可调的,在启动JVM的时候,通过-XX:AutoBoxCacheMax=<;size>来指定这个缓冲池的大小,该选项在JVN初始化的时候会设定一个名为java.lang.IntergerCache.high系统属性,然后IntergerCache初始化的时候就会读取该系统属性,然后 IntegerCache 初始化的时候就会读取该系统属性来决定上界。 [StackOverflow:Differences between new Integer(123),Interger.valueOf(123) and just 123)(https://stackoverflow.com/questions/9030817/differences-between-new-integer123-integer-valueof123-and-just-123)
String 被声明为final,因此他不可以被继承,(Interger等包装类也不能被继承) 在java8中,String 内部使用char数组存储数据。
public final class String
implements java.io.Srializable,Comparable<Sring>,CharSequence {
/**THe value is used for character storage.*/
private final char value[];
}
在java9之后,String类的实现改用byte数组存储字符串,同时使用 coder
来标识使用了哪种编码。
public final class String
implements java.io.Serializable,Comparable<String>,CharSequence {
/**The value is used for character storage.*/
private final byet[] value;
/**The ideneifier of the encoding used to encode the bytes in {
@code value}.*/
private final byte coder;
}
}
value数组被声明为final ,这意味着value数组初始化之后就不能再引用其他数组,并且String内部没有改变value数组的方法,因此可以保证String不可变
1.可以缓存hash值
因为String的hash值经常被引用,例如String用作HashMap的key。不可变的特性可以使得hash值也不可变,因此只需要进行一次计算。
2.StringPool的需要
如果一个String对象已经被创建过了,那么就会从String Pool中取得引用,只有String是不可变的,才能使用String Pool。
3.安全性 String经常作为参数,String不可变性可以保证参数不可变,例如在作为网络连接参数的情况下如果String是可变的,那么在网络连接过程中,String被改变,改变String的那一方以为现在连接的是其他主机,而实际情况却不一定是。
4. 线程安全
String 不可变性天生具备线程安全,可以在多个线程中安全地使用。
Program Creek : Why String is immutable in Java?
###String.StringBuffer and StringBuilder
1.可变性
2.线程安全
StackOverflow : String, StringBuffer, and StringBuilder
字符串常量池(String Pool)保存着所有的字符串字面量(literal strings),这些字面量在编译时期就确定。不仅如此,还可以使用String 的intern()方法在运行过程中将字符串添加到String Pool 中。
当一个字符串调用intern()方法时,如果String Pool中已经存在一个字符串和该字符串值相等(使用equals()方法进行确定),那么就会返回String Pool中字符串的引用;否则,就会在String Pool中添加一个新的字符串,并返回这个新字符串的引用。、
下面示例中,s1和s2采用new string()的方式新建了两个不同的字符串,而s3和s4是通过s1.intern()和s2.intern()方法取得同一个字符串引用。intern()首先把“aaa”放到String Pool中,然后返回这个字符串引用,因此s3和s4引用的是同一个字符串。 ···java String s1 = new String("aaa"); String s2 = new String("aaa"); System.out.println(s1==s2);//false String s3 = s1.intern(); String s4 = s1.intern(); System.out.println(s3=s4);//true
如果是采用“bbb“这种字面量的形式创建字符串,会自动地将字符串放入String Pool中
···java
String s5= "bbb";
String s6="bbb";
System.out.println(s5=s6);//true
在java 7之前,String Pool被放在运行时的常量池中,他属于永久代,而在Java7中,String Pool被移到堆中,这是因为永久代的空间有限,在大量使用字符串的场景下会导致OutOfMemeoryError错误。
使用这种方式一共会创建两个字符串对象(前提是String Pool中还没有”abc“字符串对象)。
创建一个测试类,其main方法中使用这种方式来创建字符串对象。
创建一个测试类,其main方法中使用这种方式来创建字符串对象。
public class NewstringTest {
public static void main(String[] args){
String s = new String("abc");
}
}
使用javap -verbose 进行反编译,得到以下内容:
// ...
Constant pool:
// ...
#2 = class #18 //
java/lang/String
#3 = String #19 //abc
//···
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;])V
flags: ACC_PUBLIC,ACC_STATIC
Code:
stack=3,locals = 2,args_size =1
0:new #2//classjava/lang/String
3:in_depth_understanding_string_intern
4:ldc #3//String abc
6:invokespecial #4 //Methodjava/lang/String."<init>":(Ljava/lang/String;)V
9:astore_1
//...
在Constant Pool中,#19存储这字符串字面量”abc“,#3是String Pool中的字符串对象的构造函数参数时,并不会完全复制value数组内容,而是都会指向同一个value数组。
···java
public String(String original) {
this.value = original.value;
this.hash = original.hash;
}
##三、运算
###参数传递
java的参数是以值传递的形式传入方法中,而不是引用传递。
以下代码中Dog dog 的dog是一个指针,存储的是对象的地址。在将一个参数传入一个方法时,本质是将对象的地址以值的方式传递到参数中。。
···java
public class Dog{
String name;
Dog(String name){
this.name = name;
}
String getName(){
return this.name;
}
void setName(String name){
this.name= name;
}
String getObjectAddress(){
return super.toString();
}
}
在方法中改变对象的字段值会改变元对象该字段值,因为引用的是同一个对象
···java class PassByValueExample{
public static void main(String[] args){
Dog dog = new Dog("A");
func(dog);
System.out.printtln(dog.getName());//B
}
private static void func(Dog dog){
dong.setName("B");
}
}
但是在方法中将指针引用了其他对象,那么此时方法里和方法外的两个指针指向了不同的对象,在一个指针改变其所有对象的内容对另一个指针所指的对象没有影响
```java
public class PassByValueExample{
public static void main(String[] args()
Dog dog = new Dog("A");
System.out.println(dog.getObjectAddress());
func(dog);
System.out.println(dog.getObjectAddress());
}
private static void func(Dog dog){
System.out.println(dog.getObjectAddress());
dog = new Dog("B");
System.out.println(dog.getObjectAddress());
System.out.println(dog.getName());
}
StackOverflow: Is Java “pass-by-reference” or “pass-by-value”?
###float 与double
java不能隐式执行向下转型,因为这会使得精度降低。 1.1 字面量属于 double 类型,不能直接将 1.1 直接赋值给 float 变量,因为这是向下转型。
// float f = 1.1;
1.1f 字面量才是 float 类型。
float f = 1.1f;
因为字面量 1 是 int 类型,它比 short 类型精度要高,因此不能隐式地将 int 类型向下转型为 short 类型。
short s1 = 1;
// s1 = s1 + 1;
但是使用 += 或者 ++ 运算符会执行隐式类型转换。
s1 += 1;
s1++;
上面的语句相当于将 s1 + 1 的计算结果进行了向下转型:
s1 = (short) (s1 + 1);
StackOverflow : Why don't Java's +=, -=, *=, /= compound assignment operators require casting?
从 Java 7 开始,可以在 switch 条件判断语句中使用 String 对象。
String s = "a";
switch (s) {
case "a":
System.out.println("aaa");
break;
case "b":
System.out.println("bbb");
break;
}
switch 不支持 long、float、double,是因为 switch 的设计初衷是对那些只有少数几个值的类型进行等值判断,如果值过于复杂,那么还是用 if 比较合适。
// long x = 111;
// switch (x) { // Incompatible types. Found: 'long', required: 'char, byte, short, int, Character, Byte, Short, Integer, String, or an enum'
// case 111:
// System.out.println(111);
// break;
// case 222:
// System.out.println(222);
// break;
// }
StackOverflow : Why can't your switch statement data type be long, Java?
##四、关键字
1.数据
声明数据为常量,可以是编译时常量,也可以是运行时被初始化后不能被改变的常量。
final int x=1;
final A y =new A();
y.a =1;
2.方法 声明方法不能被子类重写
private 方法隐式地被指定为 final,如果在子类中定义的方法和基类中的一个 private 方法签名相同,此时子类的方法不是重写基类方法,而是在子类中定义了一个新的方法。
3. 类
声明类不允许被继承。
1. 静态变量
public class A {
private int x; // 实例变量
private static int y; // 静态变量
public static void main(String[] args) {
// int x = A.x; // Non-static field 'x' cannot be referenced from a static context
A a = new A();
int x = a.x;
int y = A.y;
}
}
2. 静态方法
静态方法在类加载的时候就存在了,它不依赖于任何实例。所以静态方法必须有实现,也就是说它不能是抽象方法。
public abstract class A {
public static void func1(){
}
// public abstract static void func2(); // Illegal combination of modifiers: 'abstract' and 'static'
}
只能访问所属类的静态字段和静态方法,方法中不能有 this 和 super 关键字,因此这两个关键字与具体对象关联。
public class A {
private static int x;
private int y;
public static void func1(){
int a = x;
// int b = y; // Non-static field 'y' cannot be referenced from a static context
// int b = this.y; // 'A.this' cannot be referenced from a static context
}
}
3. 静态语句块
静态语句块在类初始化时运行一次。
public class A {
static {
System.out.println("123");
}
public static void main(String[] args) {
A a1 = new A();
A a2 = new A();
}
}
123
4. 静态内部类
非静态内部类依赖于外部类的实例,也就是说需要先创建外部类实例,才能用这个实例去创建非静态内部类。而静态内部类不需要。
public class OuterClass {
class InnerClass {
}
static class StaticInnerClass {
}
public static void main(String[] args) {
// InnerClass innerClass = new InnerClass(); // 'OuterClass.this' cannot be referenced from a static context
OuterClass outerClass = new OuterClass();
InnerClass innerClass = outerClass.new InnerClass();
StaticInnerClass staticInnerClass = new StaticInnerClass();
}
}
静态内部类不能访问外部类的非静态的变量和方法。
5. 静态导包
在使用静态变量和方法时不用再指明 ClassName,从而简化代码,但可读性大大降低。
import static com.xxx.ClassName.*
6. 初始化顺序
静态变量和静态语句块优先于实例变量和普通语句块,静态变量和静态语句块的初始化顺序取决于它们在代码中的顺序。
public static String staticField = "静态变量";
static {
System.out.println("静态语句块");
}
public String field = "实例变量";
{
System.out.println("普通语句块");
}
最后才是构造函数的初始化。
public InitialOrderTest() {
System.out.println("构造函数");
}
存在继承的情况下,初始化顺序为:
public native int hashCode()
public boolean equals(Object obj)
protected native Object clone() throws CloneNotSupportedException
public String toString()
public final native Class<?> getClass()
protected void finalize() throws Throwable {}
public final native void notify()
public final native void notifyAll()
public final native void wait(long timeout) throws InterruptedException
public final void wait(long timeout, int nanos) throws InterruptedException
public final void wait() throws InterruptedException
两个对象具有等价关系,需要满足以下五个条件:
Ⅰ 自反性
x.equals(x); // true
Ⅱ 对称性
x.equals(y) == y.equals(x); // true
Ⅲ 传递性
if (x.equals(y) && y.equals(z))
x.equals(z); // true;
Ⅳ 一致性
多次调用 equals() 方法结果不变
x.equals(y) == x.equals(y); // true
Ⅴ 与 null 的比较
对任何不是 null 的对象 x 调用 x.equals(null) 结果都为 false
x.equals(null); // false;
2.等价与相等
Interger x = new Interger(1);
Interger y = new Interger(1);
System.out.println(x.equals(y));//true;
System.out.println(x==y);//false
3.实现
public class EqualsExample {
private int x;
private int y;
private int z;
public EqualsExample(int x;int y;int z){
this.x=x;
this y = y;
this z = z;
}
@override
public boolean equals(Object o){
if(this ==o) return true;
if(o ==null||getClass()!=o.getClass())
return false;
EqualsExample that = (EqualsExample) o;
if(x!=that.x) return false;
if(y!=that.y ) return false;
reurn z = that.z;
}
}
###hashCode()
hashCode()返回哈希值,而equals()是用来判断两个对象是否等价,等价的两个对象散列值一定相同,但是散列值相同的两个对象不一定等价,这是因为计算哈希值具有随机性,两个值不同的对象可能计算出相同的哈希值。
在覆盖equals()方法时应当总是覆盖hashCode()方法,保证等价的两个对象哈希值也相等。
HashSet和HashMap等集合类使用了hashCode()方法来计算对象应该存储的位置,因此要将对象添加到这些集合类中,需要让对应的类实现hashCode()方法。
下面的代码中,新建了两个等价的对象,并将他们添加到HashSet();我们希望将这两个对象当成一样的,只在集合中添加一个对象,但是EqualExaple没有实现hashCode()方法,因此这两个对象的哈希值是不同的,最终导致集合添加了两个等价的对象。
EqualExample e1 = new EqualExample(1,1,1);
EqualExample e2 = new EqualExample(1,1,1);
System.out.println(e1.equals(e2));//true
HashSet<EqualExample> set = new HashSet<>();
set.add(e1);
set.add(e2);
System.out.println(set.size());//2
理想的哈希函数应当具有均匀性,即不相等的对象应当均匀分布到所有可能的哈希值上,这就要求了哈希函数要把所有域的值都考虑进来,可以将每个域都当成R进制的某一位,然后组成一个R进制的整数。
R一般取31,因为他是一个奇素数,如果是偶数的话,当出现乘法溢出,信息就会丢失,因为与2相乘相当于向左移一位,最左边的位丢失,并且一个数与31相乘可以转换成移位和减法:'31*x ==(x<<5)-x,编译器会自动进行这个优化。
```java
@override
public int hashCode(){
int result = 17;
result = 31*result +x;
result = 31*result +y;
reslt = 31*result +z;
return result;
}
###toString()
默认返回ToStringExample@1554617c这种形式,其中@后面的数值为散列码的无符号十六进制表示。
public class ToStringExample {
private int number;
public ToStringExample(int number){
this.number = number;
}
}
ToStringExample example = new ToStringExample(123);
System.out.println(example.toString());
ToStringExample@4554617c
###clone()
1.cloneable
clone 是Object的protected方法,它不是public,一个类不显式去重写clone(),其他类就不能直接去调用该类实例的clone()方法。
public class CloneExample{
private int a;
private int b;
}
CloneExample e1 = new CloneExaple();
//CloneExample e2 = e1.clone();//'clone()' has protected access in 'java.lang.object'
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。