1 Star 0 Fork 0

studymemory / MyFirstProjext

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
该仓库未声明开源许可证文件(LICENSE),使用请关注具体项目描述及其代码上游依赖。
克隆/下载
Java 基础.markdown 24.62 KB
一键复制 编辑 原始数据 按行查看 历史
studymemory 提交于 2021-09-16 02:29 . 2021/9/16 10:28

Java 基础

一、数据类型

基本类型

  • byte/8
  • char/16
  • short/16
  • int/32
  • float/32
  • long/64
  • double/64
  • boolean/~

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)的区别在于:

  • new Integer(123) 每次都会新建一个对象;
  • Integer.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

基本类型对应的缓冲池如下

  • boolean values true and false
  • all byte values
  • short values between -128 and 127
  • int values between -128 and 127
  • char in the range \u0000 to \u007F

在使用这些基本类型对应的包装类型时,如果该数值范围在缓冲池范围内,就可以直接使用缓冲池中的对象。

在jdk 1.8所有的数值类缓冲池中,Interger的缓冲池IntergerCache很特殊,这个缓冲池的下界是-128,上界默认是127,但是这个上界是可调的,在启动JVM的时候,通过-XX:AutoBoxCacheMax=&lt;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

概览

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.可变性

  • String不可变
  • StringBuffer和StringBuilder可变

2.线程安全

  • String不可变,因此线程是安全的
  • StringBuilder不是线程安全的
  • StringBuffer是线程安全的,内部使用synchronized进行同步

StackOverflow : String, StringBuffer, and StringBuilder

String Pool

字符串常量池(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错误。

new String("abc")

使用这种方式一共会创建两个字符串对象(前提是String Pool中还没有”abc“字符串对象)。

  • ”abc“属于字符串字面量,因此编译时期会在String Pool中创建一个字符串对象,指向这个”abc“字符串字面量;
  • 而使用new的方法会在堆中创建一个字符串对象

创建一个测试类,其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?

switch

从 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?

##四、关键字

final

1.数据

声明数据为常量,可以是编译时常量,也可以是运行时被初始化后不能被改变的常量。

  • 对于基本类型,final使数值不变
  • 对于引用类型,final使引用不变,也就不能引用其他对象,但是被引用的对象本身是可以修改的
final int  x=1;
final A y =new A();
y.a =1;

2.方法 声明方法不能被子类重写

private 方法隐式地被指定为 final,如果在子类中定义的方法和基类中的一个 private 方法签名相同,此时子类的方法不是重写基类方法,而是在子类中定义了一个新的方法。

3. 类

声明类不允许被继承。

static

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("构造函数");
}

存在继承的情况下,初始化顺序为:

  • 父类(静态变量、静态语句块)
  • 子类(静态变量、静态语句块)
  • 父类(实例变量、普通语句块)
  • 父类(构造函数)
  • 子类(实例变量、普通语句块)
  • 子类(构造函数)

五、Object 通用方法

概览


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

equals()

两个对象具有等价关系,需要满足以下五个条件:

Ⅰ 自反性

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.等价与相等

  • 对于基本类型,==判断两个值是否相等,基本类型没有equals()方法
  • 对于引用类型,==判断两个变量是否引用同一个对象,而equals()判断引用的对象是否等价
Interger x = new Interger(1);
Interger y = new Interger(1);
System.out.println(x.equals(y));//true;
System.out.println(x==y);//false

3.实现

  • 检查是否为同一个对象的引用,如果是直接返回true;
  • 检查是否是同一个类型,如果不是,直接返回false;
  • 将Object对象进行转型
  • 判断每个关键域是否相等。
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'
Java
1
https://gitee.com/studymemory/my-first-projext.git
git@gitee.com:studymemory/my-first-projext.git
studymemory
my-first-projext
MyFirstProjext
master

搜索帮助