Java教程

java基础

本文主要是介绍java基础,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

java基础

一、数据类型

基本数据类型

  • byte,char,short,int,float,long,double,boolean

    boolean 只有两个值:true、false,可以使用 1 bit 来存储,但是具体大小没有明确规定。JVM 会在编译时期将boolean 类型的数据转换为 int,使用 1 来表示true,0 表示 false。JVM 支持 boolean 数组,但是是通过读写 byte数组来实现的。

包装类型

基本类型都有对应的包装类型,基本类型与其对应的包装类型之间的赋值使用自动装箱与拆箱完成

Integer x = 2; // 装箱 
int y = x; // 拆箱

缓存池

new Integer(123) 与 Integer.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 >= IntegerCache.low && i <= IntegerCache.high) 
        return IntegerCache.cache[i + (-IntegerCache.low)]; 
    return new Integer(i); 
}
  • 在 Java 8 中,Integer 缓存池的大小默认为 -128~127

  • 编译器会在自动装箱过程调用 valueOf() 方法,因此多个值相同且值在缓存池范围内的 Integer 实例使用自动装箱来

    创建,那么就会引用相同的对象

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

    • 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 所有的数值类缓冲池中,Integer 的缓冲池 IntegerCache 很特殊,这个缓冲池的下界是 - 128,上界默认是 127,但是这个上界是可调的,在启动 jvm 的时候,通过 -XX:AutoBoxCacheMax= 来指定这个缓冲池的大小,该选项在 JVM 初始化的时候会设定一个名为 java.lang.IntegerCache.high 系统属性,然后 IntegerCache 初始*化的时候就会读取该系统属性来决定上界

二、String

概述

String 被声明为 fifinal,因此它不可被继承。

在 Java 8 中,String 内部使用 char 数组存储数据。

public final class String implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */ 
    private final char value[]; 
}

在 Java 9 之后,String 类的实现改用 byte 数组存储字符串,同时使用 coder 来标识使用了哪种编码

public final class String implements java.io.Serializable, Comparable<String>, CharSequence { 
    /** The value is used for character storage. */ 
    private final byte[] value; 
    /** The identifier of the encoding used to encode the bytes in {@code value}. */ 
    private final byte coder; 
}

**value 数组被声明为 fifinal,这意味着 value 数组初始化之后就不能再引用其它数组。并且 String 内部没有改变 value数组的方法,因此可以保证 String 不可变 **

不变的好处

1. 可以缓存 hash

因为 String 的 hash 值经常被使用,例如 String 用做 HashMap 的 key。不可变的特性可以使得 hash 值也不可变,因此只需要进行一次计算。

2. String Pool 的需要

如果一个 String 对象已经被创建过了,那么就会从 String Pool 中取得引用。只有 String 是不可变的,才可能使用String Pool

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LHSpj5tJ-1626187609764)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210713134941518.png)]

3. 安全性

String 经常作为参数,String 不可变性可以保证参数不可变。例如在作为网络连接参数的情况下如果 String 是可变的,那么在网络连接过程中,String 被改变,改变 String 对象的那一方以为现在连接的是其它主机,而实际情况却不一定是。

4. 线程安全

String 不可变性天生具备线程安全,可以在多个线程中安全地使用。

String, StringBuffffer and StringBuilder

1. 可变性

String 不可变

StringBuffffer 和 StringBuilder 可变

2. 线程安全

  • String 不可变,因此是线程安全的

  • StringBuilder 不是线程安全的

  • StringBuffffer 是线程安全的,内部使用 synchronized 进行同步

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() 方法取得一个字符串引用。intern() 首先把 s1 引用的字符串放到 String Pool 中,然后返回这个字符串引用。因此 s3 和 s4 引用的是同一个字符串。

String s1 = new String("aaa"); 
String s2 = new String("aaa");
System.out.println(s1 == s2); //flase
// false String s3 = s1.intern();
String s4 = s1.intern(); 
System.out.println(s3 == s4); //true

如果是采用 “bbb” 这种字面量的形式创建字符串,会自动地将字符串放入 String Pool 中。

String s5 = "bbb"; 
String s6 = "bbb"; 
System.out.println(s5 == s6); // true 

在 Java 7 之前,String Pool 被放在运行时常量池中,它属于永久代。而在 Java 7,String Pool 被移到堆中。这是因为永久代的空间有限,在大量使用字符串的场景下会导致 OutOfMemoryError 错误

new String(“abc”)

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

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

  • 而使用 new 的方式会在堆中创建一个字符串对象。创建一个测试类,其 main 方法中使用这种方式来创建字符串对象。

public class NewStringTest {
    public static void main(String[] args) { 
        String s = new String("abc"); 
    } 
}

三、运算

参数传递

Java 的参数是以值传递的形式传入方法中,而不是引用传递。

如果在方法中改变对象的字段值会改变原对象该字段值,因为改变的是同一个地址指向的内容。

float double

Java 不能隐式执行向下转型,因为这会使得精度降低。

1.1 字面量属于 double 类型,不能直接将 1.1 直接赋值给 flfloat 变量,因为这是向下转型

// float f = 1.1;

1.1f 字面量才是 flfloat 类型

float f = 1.1f;

隐式类型转换

因为字面量 1 是 int 类型,它比 short 类型精度要高,因此不能隐式地将 int 类型下转型为 short 类型

short s1 = 1; // s1 = s1 + 1;

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,是因为 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; 
// 	}

四、继承

访问权限

Java 中有三个访问权限修饰符:private、protected 以及 public,如果不加访问修饰符,表示包级可见。

可以对类或类中的成员(字段以及方法)加上访问修饰符。

  • 类可见表示其它类可以用这个类创建实例对象。

  • 成员可见表示其它类可以用这个类的实例对象访问到该成员;

protected 用于修饰成员,表示在继承体系中成员对于子类可见,但是这个访问修饰符对于类没有意义。

设计良好的模块会隐藏所有的实现细节,把它的 API 与它的实现清晰地隔离开来。模块之间只通过它们的 API 进行通信,一个模块不需要知道其他模块的内部工作情况,这个概念被称为信息隐藏或封装。因此访问权限应当尽可能地使每个类或者成员不被外界访问。

如果子类的方法重写了父类的方法,那么子类中该方法的访问级别不允许低于父类的访问级别。这是为了确保可以使用父类实例的地方都可以使用子类实例,也就是确保满足里氏替换原则。

字段决不能是公有的,因为这么做的话就失去了对这个字段修改行为的控制,客户端可以对其随意修改。例如下面的例子中,AccessExample 拥有 id 公有字段,如果在某个时刻,我们想要使用 int 存储 id 字段,那么就需要修改所有的客户端代码。

public class AccessExample { 
    public String id;
}

可以使用公有的 getter 和 setter 方法来替换公有字段,这样的话就可以控制对字段的修改行为。

public class AccessExample {
    private int id; 
    public String getId() { 
        return id + ""; 
    }
    public void setId(String id) {
        this.id = Integer.valueOf(id);
    } 
}

但是也有例外,如果是包级私有的类或者私有的嵌套类,那么直接暴露成员不会有特别大的影响。

public class AccessWithInnerClassExample {
    private class InnerClass { 
        int x; 
    }
    private InnerClass innerClass;
    public AccessWithInnerClassExample() {
        innerClass = new InnerClass();
    }
    public int getValue() {
        return innerClass.x; // 直接访问
	}
}

抽象类与接口

1. 抽象类

抽象类和抽象方法都使用 abstract 关键字进行声明。如果一个类中包含抽象方法,那么这个类必须声明为抽象类。

抽象类和普通类最大的区别是,抽象类不能被实例化,需要继承抽象类才能实例化其子类。

2. 接口

接口是抽象类的延伸,在 Java 8 之前,它可以看成是一个完全抽象的类,也就是说它不能有任何的方法实现。

从 Java 8 开始,接口也可以拥有默认的方法实现,这是因为不支持默认方法的接口的维护成本太高了。在 Java 8 之前,如果一个接口想要添加新的方法,那么要修改所有实现了该接口的类。

接口的成员(字段 + 方法)默认都是 public 的,并且不允许定义为 private 或者 protected。

接口的字段默认都是 static 和 fifinal 的。

3. 比较

从设计层面上看,抽象类提供了一种 IS-A 关系,那么就必须满足里式替换原则,即子类对象必须能够替换掉所

有父类对象。而接口更像是一种 LIKE-A 关系,它只是提供一种方法实现契约,并不要求接口和实现接口的类具

有 IS-A 关系。

从使用上来看,一个类可以实现多个接口,但是不能继承多个抽象类。

接口的字段只能是 static 和 fifinal 类型的,而抽象类的字段没有这种限制。

接口的成员只能是 public 的,而抽象类的成员可以有多种访问权限。

4. 使用选择

使用接口:

  • 需要让不相关的类都实现一个方法,例如不相关的类都可以实现 Compareable 接口中的 compareTo() 方法;

  • 需要使用多重继承。

使用抽象类:

  • 需要在几个相关的类中共享代码。

  • 需要能控制继承来的成员的访问权限,而不是都为 public。

  • 需要继承非静态和非常量字段。

在很多情况下,接口优先于抽象类。因为接口没有抽象类严格的类层次结构要求,可以灵活地为一个类添加行为。并且从 Java 8 开始,接口也可以有默认的方法实现,使得修改接口的成本也变的很低。

super

  • 访问父类的构造函数:可以使用 super() 函数访问父类的构造函数,从而委托父类完成一些初始化的工作。

  • 访问父类的成员:如果子类重写了父类的某个方法,可以通过使用 super 关键字来引用父类的方法实现。

重写与重载

1. 重写(Override)

存在于继承体系中,指子类实现了一个与父类在方法声明上完全相同的一个方法。

为了满足里式替换原则,重写有以下三个限制:

  • 子类方法的访问权限必须大于等于父类方法;

  • 子类方法的返回类型必须是父类方法返回类型或为其子类型。

  • 子类方法抛出的异常类型必须是父类抛出异常类型或为其子类型。

使用 @Override 注解,可以让编译器帮忙检查是否满足上面的三个限制条件。

  • 子类方法访问权限为 public,大于父类的 protected。

  • 子类的返回类型为 ArrayList,是父类返回类型 List 的子类。

  • 子类抛出的异常类型为 Exception,是父类抛出异常 Throwable 的子类。

  • 子类重写方法使用 @Override 注解,从而让编译器自动检查是否满足限制条件。

在调用一个方法时,先从本类中查找看是否有对应的方法,如果没有查找到再到父类中查看,看是否有继承来的方法。否则就要对参数进行转型,转成父类之后看是否有对应的方法。总的来说,方法调用的优先级为:

  • this.func(this)

  • super.func(this)

  • this.func(super)

  • super.func(super)

2. 重载(Overload)

存在于同一个类中,指一个方法与已经存在的方法名称上相同,但是参数类型、个数、顺序至少有一个不同。

应该注意的是,返回值不同,其它都相同不算是重载。

这篇关于java基础的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!