基本结构篇(Java - 内部类/代码块/数据类型/变量/常量/字面量/方法/构造器)(doing)

一、内部类

外部类:单独定义的一个类,不在其他类的内部,只能用 public 和 缺省 权限修饰符

内部类:在类的内部再定义一个类,根据内部类的定义位置和修饰符的不同,可以分为:

1. 成员内部类

1.1. 语法

[修饰符] class 外部类{
    [修饰符] class 内部类{    }
}

1.2. 特点

1、不能使用static关键字,但是可以使用 static final关键字定义常量

2、内部类可以直接访问外部类的成员(包括私有成员)

3、内部类如果想要调用外部类的方法,需要使用 外部类名.this.外部方法名

4、可以使用final修饰内部类,表示不能被继承

5、编译以后也会有自己独立的字节码文件, Outer$Inner.class

6、外部函数的static成员的,不允许访问成员的内部类

7、可以在外部类里创建内部类对象,然后在通过方法返回,同事,也可以使用 new Outer().new Inner() 在外部

直接访问内部类

public class Test {
    public static void main(String[] args) {
        // 直接使用 new Outer().new Inner() 方法创建一个Inner对象
        Outer.Inner inner1 = new Outer().new Inner();  
        
        // 创建一个 Outer 对象,
        //再调用 Outer 对象的 getInner方法,在getInner方法里创建并返回 Inner 对象
        Outer.Inner inner2 = new Outer().getInner();
    }
}
class Outer {
    int age = 19;
    private String name = "jack";
	
    private void test() {
        System.out.println("我是Outer类里的test方法");
    }
    
    class Inner {  // 可以把 Inner 想象成为和 age / name一样,只不过它的数据类型是class
        // static int a = 10;  报错,不能使用 static
        static final int b = 1;  // 可以使用 static final 定义常量
        private void test() {
            System.out.println("我是Inner类里的test方法");
        }
        public void demo() {
            // 内部类可以直接访问外部类的成员,包括私有成员。
            System.out.println(name);
            this.test();  // 等价于 test() 调用的是内部类的test方法
            Outer.this.test(); // 需要使用 Outer.this 调用外部类的 test 方法
        }
    }
    public static void foo() {
        // Inner inner = new Inner();  报错!外部static成员不允许访问非静态内部类
    }
    public Inner getInner() {
        return new Inner();
    }
}

1.3. 示例

//成员内部类
public class MemberOuter {
    private int x = 10;
    private static int m = 5;
 
    public final class MemberInner {
        private int a = 2;
        // private static int b = 4;
        private static final int c = 5;
        int x = 3;
 
        public void demo() {
            int x = 1;
            System.out.println("MemberInner类demo方法里的x = " + x);
            System.out.println("MemberInner类demo方法里的this.x = " + this.x);
            System.out.println("MemberInner类demo方法里的MemberOuter.this.x = " + MemberOuter.this.x);
            System.out.println(m);
            xxx();
        }
    }
 
    public void test() {
        MemberInner inner = new MemberInner();
        inner.demo();
    }
 
    public static void xxx() {
    }
}
public class InnerDemo {
    public static void main(String[] args) {
 
        
        //创建成员内部类的对象必须先创建外部类对象!
        //创建成员内部类对象必须要先创建外部类对象
 
        //方式a:在外部类的内部创建对象并使用
        //方式b: 也可以直接创建一个成员内部类对象(和权限修饰符有关)
                //外部类名.内部类名  变量名 = 外部类对象名.new 成员内部类名();
        MemberOuter memberOuter = new MemberOuter();
        memberOuter.test();
 
        // MemberOuter.MemberInner memberInner = memberOuter.new MemberInner();
        MemberOuter.MemberInner memberInner = new MemberOuter().new MemberInner();
 
        
    }
}

1.4. 总结

成员内部类:

1、和成员变量一样,可以使用全部的四种权限修饰符和final修饰符

2、在成员内部类里可以访问外部类的所有成员变量,调用所有方法,包括静态的。

3、在成员内部类里可以定义非静态成员变量,也可以使用static final 定义静态成员常量,但是不能使用static定

义静态变量

4、调用成员内部类的方法和访问成员内部类里变量,需要先创建成员内部类对象

创建成员内部类对象必须要先创建外部类对象

a. 在外部类的内部创建对象并使用

MemberOuter memberOuter = new MemberOuter();
memberOuter.test();

b. 也可以直接创建一个成员内部类对象(和权限修饰符有关)

外部类名.内部类名 变量名 = 外部类对象名.new 成员内部类名();

MemberOuter.MemberInner memberInner = new MemberOuter().new MemberInner();

5、在成员内部类里出现 内部类成员变量、内部类局部变量、外部类成员变量同名的情况

  • 在方法里直接访问,访问的是局部变量
  • 使用 this.变量名 访问内部类的成员变量
  • 使用 外部类名.this.变量名 访问外部类的成员变量

6、成员内部类编译以后也会生成一个 .class文件,文件名是 外部类名$内部类名.class。

2. 静态内部类

静态内部类也被成为嵌套类,静态内部类不会持有外部类对象的引用

2.1. 语法

[修饰符] class 外部类{    
    [其他修饰符] static class 内部类{    }
}

2.2. 特点

1、使用static关键字修饰

2、在静态内部类里,可以使用static关键字定义静态成员

3、只能访问外部的静态成员,不能访问外部的非静态成员

4、外部类可以通过 静态内部类名.静态成员名 访问静态内部类的静态成员

class Outer {
    int age = 19;
    static String type = "outer";
 
    static class Inner {  // 需要使用 static 关键字
        static int x = 1;  // 能够定义静态变量
        public static void test() {
            // 只能访问外部的静态变量,不能访问非静态变量
            // System.out.println(age);
            System.out.println(type);
        }
    }
    public void  demo() {
        // 外部类可以直接通过 内部类.静态变量名,不需要创建对象
        System.out.println(Inner.x);  
    }
}

2.3. 示例

public class StaticOuter {
    private int x = 10;
    private static int m = 3;
 
    public static final class StaticInner {
        private int a = 5;
        private static int b = 7;
        private int x = 1;
        private static int m = 6;
 
        public void test() {
            // System.out.println(x);
            // foo();
            int x = 2;
            int m = 9;
            System.out.println("StaticInner类test方法里 x = " + x);  // 2
            System.out.println("StaticInner类test方法里 x = " + this.x);  // 1
            System.out.println("StaticInner类test方法里 m = " + m);  // 9
            System.out.println("StaticInner类test方法里 StatiInner.m = " + StaticInner.m);  // 6
            System.out.println("StaticInner类test方法里 StaticOuter.m = " + StaticOuter.m);  // 3
            demo();
        }
 
        public static void bar() {
            // System.out.println(x);
        }
    }
 
    public static void demo() {
    }
 
    public void foo() {
    }
}
public class InnerDemo {
    public static void main(String[] args) {
        MemberOuter memberOuter = new MemberOuter();
        memberOuter.test();
 
        // MemberOuter.MemberInner memberInner = memberOuter.new MemberInner();
        MemberOuter.MemberInner memberInner = new MemberOuter().new MemberInner();
 
        StaticOuter.StaticInner staticInner = new StaticOuter.StaticInner();
        StaticOuter.StaticInner.bar();
 
    }
}

2.4. 总结

静态内部类:

1、定义的位置和成员内部类的位置一样,使用static关键字修饰

  • 语法上可以理解为 成员内部类前面使用static修饰

2、可以使用 四种权限修饰符和 final修饰符

  • 不能访问外部类的非静态成员变量,可以访问外部类的静态成员变量
  • 总结:静态只能访问静态的。不能访问非静态的

3、可以定义  非静态成员变量,也可以定义静态成员变量

4、创建静态内部类对象,可以不创建外部类对象

  • 静态内部类就像是一个单独的类,只不过借用了外部类的命名空间
StaticOuter.StaticInner staticInner = new StaticOuter.StaticInner();

5、也会生成独立的 .class文件,文件名是 外部类名.静态内部类名

3. 局部内部类

局部内部类定义在外部类的方法中,就像局部变量一样,并不是外部类的成员。

局部内部类在方法外是无法访问到的,但它的实例可以从方法中返回,并且实例在不在被引用之前会一直存在。

3.1. 语法

[修饰符] class 外部类{
    [修饰符] 返回值类型 方法名([形参列表]){
            [final/abstract] class 内部类{
        }
    }    
}

3.2. 特点

1、定义在类的某个方法里,而不是直接定义在类里

2、局部内部类前面不能有权限修饰符

3、局部内部类里不能使用static声明变量

4、局部内部类能访问外部类的静态成员

5、如果这个局部内部类所在的方法是静态的,它无法访问外部类的非静态成员

6、局部内部类可以访问外部函数的局部变量,但这个局部变量必须要非final修饰。JDK8以后,final可以省略

class Outer {
    int age = 19;
	static int m = 10;
    
    public void test() {
        // final String y = "good";
        String y = "good";  // JDK8 以后,final可以省略
        class Inner {  // 定义在外部类的某个方法里
            // static int a = 10; 不能定义静态变量!  
            private  void demo() {
                System.out.println(age);
                // 不能修改外部函数的局部变量
				// y = 'yes';
                // 只能访问被 final 修饰的外部函数的局部变量
                // JDK8 以后,如果外部函数的局部变量没有加 final,编译器会自动加 final
                System.out.println(y);
            }
        }
        Inner inner = new Inner();
        inner.demo();
    }
}

3.3. 示例

public class LocalOuter {
    private int x = 6;
 
    public void test() {
        int x = 10;
        int y = 0;
 
        class LocalInner {
            private int a = 1;
            // private static int b = 2;
            private static final int c = 3;
            private int x = 5;
 
            public void demo() {
                int x = 1;
                System.out.println("demo方法里的x = " + x);  // 1
                System.out.println("demo方法里的this.x = " + this.x);  // 5
                System.out.println("外部类里的成员变量x = " + LocalOuter.this.x); // 6
                // 如果出现同名的情况,无法再访问 方法里的同名局部变量
                System.out.println(y);
            }
        }
        // y = 3; 局部变量 y 被局部内部类使用了,不能再修改了
 
        LocalInner localInner = new LocalInner();
        localInner.demo();
    }
}

3.4. 总结

局部内部类:

1、定义在一个方法里,和局部变量 定义的位置相同

2、不能使用任何的权限修饰符和 static修饰符

3、可以定义非静态成员变量、静态成员变量  但不能定义静态变量

4、局部内部类可以访问所在方法里的局部变量

  • 但是如果出现同名的情况
  • 访问 局部内部类里的局部变量直接写变量名
  • 访问 局部内部类里的成员变量使用 this.变量名
  • 访问 外部类里的成员变量,使用 外部类名.this.变量名
  • 不能访问 局部内部类所在方法的同名局部 变量

5、被局部内部类访问的局部变量,需要被final修饰。JDK8以后,final可以省略

6、局部内部类只能在声明它的那个方法里使用

7、生成独立的.class文件,文件名是 外部类名.编号局部内部名

4. 匿名内部类

用来创建 一个接口或者抽象类 对象

(抽象类或者接口 本不可以直接创建对象,可使用匿名内部类方法直接创建)

4.1. 语法

new 父类名或者接口名(){   
    // 方法重写
    @Override 
    public void method() {
        // 执行语句
    }
};

4.2. 特点

1、匿名内部类就是一种特殊的局部内部类,只不过没有名称而已,基本特点和局部内部类一致

2、匿名内部类不能有构造器,匿名内部类没有类名,肯定无法声明构造器

3、匿名内部类的前提是,这个内部类必须要继承自一个父类或者父接口

4、匿名内部类是接口的一种常见简化写法,也是我们开发中最常使用的一种内部类。

它的本质是一个实现类父类或者父类接口具体方法的一个匿名对象。

abstract class Animal{
    abstract void shout();
}
 
class Demo{
    public void demo(){
        Animal animal = new Animal() {
            @Override
            void shout() {
                System.out.println("动物在叫");
            }
        };
    }
}

4.3. 示例

AInterface接口

public interface AInterface {
    void test();
}

用AInterface直接创建类,用匿名内部类方法

public class AnonymousDemo {
 
    public static void main(String[] args) {
        AnonymousDemo x = new AnonymousDemo();
        x.demo();
    }
 
    public void demo(){
 
        /*class Son implements AInterface{
            @Override
            public void test() {
            }
        }
        //Son s = new Son();s.test();
        new Son().test();*/
 
        //匿名内部类
        AInterface a = new AInterface() {
            @Override
            public void test() {
                System.out.println("helloA");
            }
        };
 
        //对象a的类型不是AInterface类型,而是AInterface类型的子类!这个类没有名字
        a.test();
        System.out.println(a.getClass());
 
 
        //匿名内部类 + 匿名对象 :  可以将一个方法当作另一个方法的参数使用
        new AInterface() {
            @Override
            public void test() {
                System.out.println("helloB");
            }
        }.test();
    }
}

4.4. 总结

匿名内部类的作用:可以将一个方法当作另一个方法的参数使用

public class AnonymousTest {
 
    public static void main(String[] args) {
        AnonymousTest test = new AnonymousTest();
 
        Calculator c = new Calculator() {
            @Override
            public int doCalculator(int a, int b) {
                return a+b;
            }
        };
        int x = test.calculate(1,9,c);
 
        //等价
        int y = test.calculate(1, 9, new Calculator() {
            @Override
            public int doCalculator(int a, int b) {
                return a+b;
            }
        });
 
        System.out.println(x);
    }
 
    public int calculate(int a,int b,Calculator calculator){
        return calculator.doCalculator(a, b);
    }
}
 
interface Calculator{
    int doCalculator(int a,int b);
}

二、代码块

代码块是类的5大成分之一(成员变量、构造器,方法,代码块,内部类), 定义在类中方法外

所谓代码块,就是用大括号{}将多行代码封装在一起,形成一个独立的数据体,用于实现特定的算法。

一般来说,代码块是不能单独运行的,它必须有运行主体

在Java中代码块主要分为四种:普通代码块、静态代码块、同步代码块、构造代码块。

1. 普通代码块

普通代码块就是类中方法的方法体;普通代码块是不能够单独存在的,他必须紧跟在方法名后面,同时也必须使

用方法名调用它。

public class Test {
    public void test(){
        System.out.println("普通代码块");
    }
}

2. 静态代码块

静态代码块就是用static修饰的用{}括起来的代码段,他的主要目的就是对静态属性进行初始化。

静态代码块在第一次加载此类的时候被执行,并且只会执行一次,静态代码块优先优先于构造代码块执行。

public class Test {
    static{
        System.out.println("静态代码块");
    }
}

3. 同步代码块

同步代码块,就是使用synchronized关键字修饰的用{}括起来的代码片段,他表示同一时间只能有一个线程进入到

该方法块中,是一种多线程保护机制。在多线程环境下,对共享数据进行读写操作是需要互斥进行的,否则会导

致数据的不一致性,常见的是synchronized用来修饰方法,其语义是任何线程进入synchronized需要先取得对象

锁,如果对象锁被占用了,则阻塞等待。synchronized实现了互斥访问共享资源,但synchronized也是有代价

的,会造成系统性能的降低。

public class Test {
     synchronized(obj){ 
        System.out.println("同步代码块");
    } 
}

4. 构造代码块

构造代码块就是用{}括起来的代码片段,构造代码块会在创建对象时被调用,每次创建对象时会被调用,并且优先

于类构造函数执行。

构造代码块中定义的变量是局部变量。

new一个对象的时候总是先执行构造代码,在执行构造函数。但是有一点需要注意的是,构造代码块不是在构造函

数之前运行的,它是依托构造函数执行的。正式由于构造代码块有这几个特性,所以它常用语如下场景:

4.1. 初始化实例变量

如果一个类中存在若干个构造函数,这些构造函数都需要对实例变量进行初始化,如果我们直接在构造函数中实

例化,必定会产生很多重复代码,繁琐和可读性差。这时,我们可以充分利用构造代码块来实现,这是利用编译

器会将构造代码块添加到每个构造函数中的特性。

4.2. 初始化实例环境

一个对象必须在适当的场景下才能存在,如果没有适当的场景,则就需要在创建对象时创建此场景,我们可以利

用构造代码块来创建此场景,尤其是该场景的创建过程较为复杂的时候。

上面两个常用场景都充分利用构造代码块的特性,能够很好的解决在实例化对象时构造函数比较难解决的问题,

利用构造代码不仅可以减少代码量,同时也是程序的可读性增强了。特别是当一个对象的创建过程比较复杂,需

要实现一些复杂逻辑,这个时候如果在构造函数中实现逻辑,这是不推荐的,因为我们提倡构造函数要尽可能的

简单易懂,所以我们可以使用构造代码封装这些逻辑实现部分。

5. 参考文献

参考

三、数据类型

1. Java有哪些数据类型

定义:Java语言是强类型语言,对于每一种数据都定义了明确的具体的数据类 型,在内存中分配了不同 大小的内

存空间。

分类:两种数据类型

基本数据类型:

  • 数值型
    整数类型(byte,short,int,long)
    浮点类型(float,double)
  • 字符型(char)
  • 布尔型(boolean)

引用数据类型

  • 类(class)
  • 接口(interface)
  • 枚举(enum)
  • 注解(标注)
  • 数组

四种引用

  • 强引用
  • 软引用
  • 弱引用
  • 虚引用

2. 8 种基本数据类型

Java基本类型共有八种,可以分为三类:

  • 字符类型:char
  • 布尔类型:boolean
  • 数值类型:byte、short、int、long、float、double。

数值类型又可以分为整数类型byte、short、int、long和浮点数类型float、double。

JAVA中的数值类型不存在无符号的,它们的取值范围是固定的,不会随着机器硬件环境或者操作系统的改变而改

变。

实际上,JAVA中还存在另外一种基本类型void,它也有对应的包装类 java.lang.Void,不过我们无法直接对它们

进行操作。

8 中类型表示范围如下:

  • byte:8位,最大存储数据量是255,存放的数据范围是-128~127之间。
  • short:16位,最大数据存储量是65536,数据范围是-32768~32767之间。
  • int:32位,最大数据存储容量是2的32次方减1,数据范围是负的2的31次方到正的2的31次方减1。
  • long:64位,最大数据存储容量是2的64次方减1,数据范围为负的2的63次方到正的2的63次方减1。
  • float:32位,数据范围在3.4e-45~1.4e38,直接赋值时必须在数字后加上f或F。
  • double:64位,数据范围在4.9e-324~1.8e308,赋值时可以加d或D也可以不加。
  • boolean:只有true和false两个取值。
  • char:16位,存储Unicode码,用单引号赋值。

Java决定了每种简单类型的大小。这些大小并不随着机器结构的变化而变化。

这种大小的不可更改正是Java程序具有很强移植能力的原因之一。

下表列出了Java中定义的简单类型、占用二进制位数及对应的封装器类。

简单类型

boolean

byte

char

short

Int

long

float

double

void

二进制位数

1

8

16

16

32

64

32

64

--

封装器类

Boolean

Byte

Character

Short

Integer

Long

Float

Double

Void

对于数值类型的基本类型的取值范围,我们无需强制去记忆,因为它们的值都已经以常量的形式定义在对应的包

装类中了。

如:

  • 基本类型byte 二进制位数:Byte.SIZE最小值:Byte.MIN_VALUE最大值:Byte.MAX_VALUE
  • 基本类型short二进制位数:Short.SIZE最小值:Short.MIN_VALUE最大值:Short.MAX_VALUE
  • 基本类型char二进制位数:Character.SIZE最小值:Character.MIN_VALUE最大值:Character.MAX_VALUE
  • 基本类型double 二进制位数:Double.SIZE最小值:Double.MIN_VALUE最大值:Double.MAX_VALUE

注意:

float、double两种类型的最小值与Float.MIN_VALUE、  Double.MIN_VALUE的值并不相同,

实际上Float.MIN_VALUE和Double.MIN_VALUE分别指的是 float和double类型所能表示的最小正数。

也就是说存在这样一种情况,0到±Float.MIN_VALUE之间的值float类型无法表示,

0 到±Double.MIN_VALUE之间的值double类型无法表示。

这并没有什么好奇怪的,因为这些范围内的数值超出了它们的精度范围。

Float和Double的最小值和最大值都是以科学记数法的形式输出的,结尾的"E+数字"表示E之前的数字要乘以10的

多少倍。

比如3.14E3就是3.14×1000=3140,3.14E-3就是3.14/1000=0.00314。

Java基本类型存储在栈中,因此它们的存取速度要快于存储在堆中的对应包装类的实例对象。

从Java5.0(1.5)开始,JAVA虚拟机(JavaVirtual  Machine)可以完成基本类型和它们对应包装类之间的自动

转换。

因此我们在赋值、参数传递以及数学运算的时候像使用基本类型一样使用它们的包装类,但这并不意味着你可以

通过基本类型调用它们的包装类才具有的方法。

另外,所有基本类型(包括void)的包装类都使用了final修饰,因此我们无法继承它们扩展新的类,也无法重写

它们的任何方法。

基本类型的优势:数据存储相对简单,运算效率比较高

包装类的优势:有的容易,比如集合的元素必须是对象类型,满足了java一切皆是对象的思想

2.1. byte 类型

byte 类型表示 8 位有符号整数,取值范围为 -128 到 127。

例如:

byte a = 100;

2.2. short 类型

short 类型表示 16 位有符号整数,取值范围为 -32768 到 32767。

例如:

short b = 10000;

2.3. int 类型

int 类型表示 32 位有符号整数,默认值为 0。取值范围为 -2147483648 到 2147483647。

例如:

int c = 100000;

2.4. long 类型

long 类型表示 64 位有符号整数,取值范围为 -9223372036854775808 到 9223372036854775807。

例如:

long d = 100000000L;

需要注意的是,如果不加 L 后缀,编译器会将该字面量解析为 int 类型,从而引发编译错误。

2.5. float 类型

float 类型表示单精度浮点数,取值范围比 int 类型高,但精度只有 6~7 位有效数字。

例如:

float e = 3.14F;

需要注意的是,如果不加 F 后缀,编译器会将该字面量解析为 double 类型,从而引发编译错误。

2.6. double 类型

double 类型表示双精度浮点数,取值范围比 float 类型高,精度有 15~16 位有效数字。

例如:

double f = 3.14;

2.7. char 类型

char 类型表示单个字符,使用单引号括起来。在 Java 中,字符采用 Unicode 编码,一个 char 类型变量可以存

储 UTF-16 码位中的任意一个字符。

例如:

char g = 'A';

2.8. boolean 类型

boolean 类型表示布尔值,取值为 true 或 false。

例如:

boolean h = true;

2.9. 总结

Java 中有 8 种基本数据类型:byte、short、int、long、float、double、char 和 boolean。

在定义变量时,需要选择适当的数据类型,以确保程序可以正确地处理和存储数据。

  • 随便写一个整数字面值,默认是int类型的,如果希望随便写一个整数默认是long型的必须在数据后加L或者l表示
  • 随便写一个小数字面值,默认是double类型的,如果希望这个小数是float类型的,必须在数据后加F或者f表示

3. 引用数据类型

Java 语言中除了基本数据类型外,还有四种引用数据类型,分别为类、接口、数组和枚举。

3.1. 类

类是 Java 中最基本的引用数据类型,通过定义类可以创建对象。在 Java 中,类具有属性和方法,可以通过关键

字 class 来定义。

例如:

public class MyClass {
    private int x;
    public MyClass(int x) {
        this.x = x;
    }
    public int getX() {
        return x;
    }
}

在这个例子中,我们使用 class 关键字定义了一个名为 MyClass 的类,并定义了一个私有属性 x 和一个公共方法

getX()。

3.2. 接口

接口是 Java 中另外一种重要的引用数据类型,它定义了一组方法的规范,但不提供具体的实现。在 Java 中,可

以使用 interface 关键字来定义接口。

例如:

public interface MyInterface {
    int add(int a, int b);
}

在这个例子中,我们使用 interface 关键字定义了一个名为 MyInterface 的接口,并定义了一个抽象方法 add()。

3.3. 枚举

枚举是一种特殊的引用数据类型,它表示一组固定的值。在 Java 中,可以使用 enum 关键字来定义枚举类型。

例如:

public enum MyEnum {
    MONDAY,
    TUESDAY,
    WEDNESDAY,
    THURSDAY,
    FRIDAY,
    SATURDAY,
    SUNDAY
}

在这个例子中,我们使用 enum 关键字定义了一个名为 MyEnum 的枚举类型,并定义了 7 个枚举常量。

3.4. 注解

注解篇 - 注解机制

3.5. 数组

容器篇 - 数组

3.6. 4种引用

3.6.1. 简介

java中有值类型也有引用类型,引用类型一般是针对于java中对象来说的,今天介绍一下java中的引用类型。

java为引用类型专门定义了一个类叫做Reference。Reference是跟java垃圾回收机制息息相关的类,

通过探讨Reference的实现可以更加深入的理解java的垃圾回收是怎么工作的。

本文先从java中的四种引用类型开始,一步一步揭开Reference的面纱。

java中的四种引用类型分别是:强引用,软引用,弱引用和虚引用。

3.6.2. 详解
强引用(Strong Reference)

java中的引用默认就是强引用,任何一个对象的赋值操作就产生了对这个对象的强引用。

我们看一个例子:

public class StrongReferenceUsage {

    @Test
    public void stringReference(){
        Object obj = new Object();
    }
}

上面我们new了一个Object对象,并将其赋值给obj,这个obj就是new Object()的强引用。

强引用的特性是只要有强引用存在,被引用的对象就不会被垃圾回收。

软引用(Soft Reference)

软引用在java中有个专门的SoftReference类型,软引用的意思是只有在内存不足的情况下,被引用的对象才会被

回收。

先看下SoftReference的定义:

public class SoftReference<T> extends Reference<T>

SoftReference继承自Reference。

它有两种构造函数:

    public SoftReference(T referent)

和:

    public SoftReference(T referent, ReferenceQueue<? super T> q)

第一个参数很好理解,就是软引用的对象,第二个参数叫做ReferenceQueue,是用来存储封装的待回收

Reference对象的,ReferenceQueue中的对象是由Reference类中的ReferenceHandler内部类进行处理的。

我们举个SoftReference的例子:

    @Test
    public void softReference(){
        Object obj = new Object();
        SoftReference<Object> soft = new SoftReference<>(obj);
        obj = null;
        log.info("{}",soft.get());
        System.gc();
        log.info("{}",soft.get());
    }

输出结果:

22:50:43.733 [main] INFO com.flydean.SoftReferenceUsage - java.lang.Object@71bc1ae4
22:50:43.749 [main] INFO com.flydean.SoftReferenceUsage - java.lang.Object@71bc1ae4

可以看到在内存充足的情况下,SoftReference引用的对象是不会被回收的。

弱引用(weak Reference)

weakReference和softReference很类似,不同的是weekReference引用的对象只要垃圾回收执行,就会被回收,

而不管是否内存不足。

同样的WeakReference也有两个构造函数:

public WeakReference(T referent);

public WeakReference(T referent, ReferenceQueue<? super T> q);

含义和SoftReference一致,这里就不再重复表述了。

我们看下弱引用的例子:

    @Test
    public void weakReference() throws InterruptedException {
        Object obj = new Object();
        WeakReference<Object> weak = new WeakReference<>(obj);
        obj = null;
        log.info("{}",weak.get());
        System.gc();
        log.info("{}",weak.get());
    }

输出结果:

22:58:02.019 [main] INFO com.flydean.WeakReferenceUsage - java.lang.Object@71bc1ae4
22:58:02.047 [main] INFO com.flydean.WeakReferenceUsage - null

我们看到gc过后,弱引用的对象被回收掉了。

虚引用

PhantomReference的作用是跟踪垃圾回收器收集对象的活动,在GC的过程中,如果发现有

PhantomReference,GC则会将引用放到ReferenceQueue中,由程序员自己处理,当程序员调用

ReferenceQueue.pull()方法,将引用出ReferenceQueue移除之后,Reference对象会变成Inacti

看一个PhantomReference的例子:

@Slf4j
public class PhantomReferenceUsage {

    @Test
    public void usePhantomReference(){
        ReferenceQueue<Object> rq = new ReferenceQueue<>();
        Object obj = new Object();
        PhantomReference<Object> phantomReference = new PhantomReference<>(obj,rq);
        obj = null;
        log.info("{}",phantomReference.get());
        System.gc();
        Reference<Object> r = (Reference<Object>)rq.poll();
        log.info("{}",r);
    }
}

运行结果:

07:06:46.336 [main] INFO com.flydean.PhantomReferenceUsage - null
07:06:46.353 [main] INFO com.flydean.PhantomReferenceUsage - java.lang.ref.PhantomReference@136432db

我们看到get的值是null,而GC过后,poll是有值的。

因为PhantomReference引用的是需要被垃圾回收的对象,所以在类的定义中,get一直都是返回null:

    public T get() {
        return null;
    }
Reference和ReferenceQueue

讲完上面的四种引用,接下来我们谈一下他们的父类Reference和ReferenceQueue的作用。

Reference是一个抽象类,每个Reference都有一个指向的对象,在Reference中有5个非常重要的属性:

referent,next,discovered,pending,queue。

private T referent;         /* Treated specially by GC */
volatile ReferenceQueue<? super T> queue;
Reference next;
transient private Reference<T> discovered;  /* used by VM */
private static Reference<Object> pending = null;

每个Reference都可以看成是一个节点,多个Reference通过next,discovered和pending这三个属性进行关联。

先用一张图来对Reference有个整体的概念:

referent就是Reference实际引用的对象。

通过next属性,可以构建ReferenceQueue。

通过discovered属性,可以构建Discovered List。

通过pending属性,可以构建Pending List。

四大状态

在讲这三个Queue/List之前,我们先讲一下Reference的四个状态:

从上面的图中,我们可以看到一个Reference可以有四个状态。

因为Reference有两个构造函数,一个带ReferenceQueue,一个不带。

    Reference(T referent) {
        this(referent, null);
    }

    Reference(T referent, ReferenceQueue<? super T> queue) {
        this.referent = referent;
        this.queue = (queue == null) ? ReferenceQueue.NULL : queue;
    }

对于带ReferenceQueue的Reference,GC会把要回收对象的Reference放到ReferenceQueue中,

后续该Reference需要程序员自己处理(调用poll方法)。

不带ReferenceQueue的Reference,由GC自己处理,待回收的对象其Reference状态会变成Inactive。

创建好了Reference,就进入active状态。

active状态下,如果引用对象的可到达状态发送变化就会转变成Inactive或Pending状态。

Inactive状态很好理解,到达Inactive状态的Reference状态不能被改变,会等待GC回收。

Pending状态代表等待入Queue,Reference内部有个ReferenceHandler,会调用enqueue方法,将Pending对

象入到Queue中。

入Queue的对象,其状态就变成了Enqueued。

Enqueued状态的对象,如果调用poll方法从ReferenceQueue拿出,则该Reference的状态就变成了Inactive,等

待GC的回收。

这就是Reference的一个完整的生命周期。

三个Queue/List

有了上面四个状态的概念,我们接下来讲三个Queue/List:

ReferenceQueue,discovered List和pending List。

ReferenceQueue在讲状态的时候已经讲过了,它本质是由Reference中的next连接而成的。

用来存储GC待回收的对象。

pending List就是待入ReferenceQueue的list。

discovered List这个有点特别,在Pending状态时候,discovered List就等于pending List。

在Active状态的时候,discovered List实际上维持的是一个引用链。通过这个引用链,我们可以获得引用的链式

结构,当某个Reference状态不再是Active状态时,需要将这个Reference从discovered List中删除。

WeakHashMap

最后讲一下WeakHashMap,WeakHashMap跟WeakReference有点类似,在WeakHashMap如果key不再被使

用,被赋值为null的时候,该key对应的Entry会自动从WeakHashMap中删除。

我们举个例子:

    @Test
    public void useWeakHashMap(){
        WeakHashMap<Object, Object> map = new WeakHashMap<>();
        Object key1= new Object();
        Object value1= new Object();
        Object key2= new Object();
        Object value2= new Object();

        map.put(key1, value1);
        map.put(key2, value2);
        log.info("{}",map);

        key1 = null;
        System.gc();
        log.info("{}",map);

    }

输出结果:

[main] INFO com.flydean.WeakHashMapUsage - {java.lang.Object@14899482=java.lang.Object@2437c6dc, java.lang.Object@11028347=java.lang.Object@1f89ab83}
[main] INFO com.flydean.WeakHashMapUsage - {java.lang.Object@14899482=java.lang.Object@2437c6dc}

可以看到gc过后,WeakHashMap只有一个Entry了。

总结

在 Java 中有 4 种引用类型:强引用、软引用、弱引用和虚引用。

根据需求选择适当的引用类型可以更好地实现程序功能并提高实际运行效率。

3.6.3. 应用场场景

不同的引用类型有着不同的应用场景,以下为 4 种引用的常见应用

强引用

强引用是 Java 默认的引用类型,它们可以使对象在程序运行期间一直存在,并且在垃圾回收时不会被回收掉。

因此,强引用通常用于需要永久保留的对象,例如单例模式实现中的唯一实例。

public class Singleton {
    private static final Singleton INSTANCE = new Singleton();
    private Singleton() {}
    public static Singleton getInstance() {
        return INSTANCE;
    }
}

在这个例子中,我们使用了一个私有构造函数来限制创建多个实例,并将 Singleton 类的唯一实例保存在一个名

为 INSTANCE 的静态变量中,由于该变量是强引用类型,保证了该实例在程序运行期间一直存在。

软引用

软引用适合用于缓存数据,当内存空间不足时,GC 会回收掉被软引用关联的对象。

软引用通常用于适合使用缓存的场景,例如图片缓存、数据缓存等。

SoftReference<Bitmap> bitmapRef = new SoftReference<>(loadBitmap());

在这个例子中,我们使用 SoftReference 类创建了一个名为 bitmapRef 的软引用,

并将其指向 loadBitmap() 方法返回的 Bitmap 对象。

如果内存空间充足,该对象可以保持其存在状态;如果内存空间不足,垃圾回收器会自动回收与 bitmapRef 关联

的 Bitmap 对象。

弱引用

弱引用适合用于实现缓存功能。使用弱引用可以避免由于缓存数据过期而导致的内存泄漏。

因为一旦对象没有其他的强引用,垃圾回收器就会立即回收被弱引用关联的对象。

WeakReference<CachedObject> cacheRef = new WeakReference<>(cachedObject);

在这个例子中,我们使用 WeakReference 类创建了一个名为 cacheRef 的弱引用,并将其指向 cachedObject 对

象。当 cachedObject 对象没有其他强引用时,缓存对象会被立即回收。

虚引用

虚引用通常用于跟踪对象是否已经被垃圾回收器回收,它本身并不对对象的生命周期产生影响。

可以使用 PhantomReference 类和 ReferenceQueue 来实现虚引用的功能。

PhantomReference<SomeObject> phantomRef = new PhantomReference<>(someObject, referenceQueue);

在这个例子中,我们使用 PhantomReference 类创建了一个名为 phantomRef 的虚引用,并将其指向

someObject 对象,同时将其与一个 ReferenceQueue 对象关联。

当 someObject 对象被 GC 回收后,phantomRef 会被添加到 referenceQueue 队列中,程序可以通过测该队列

来判断对象是否已被回收。

总结

不同的引用类型在不同场景下有着不同的应用。合理地选择适当的引用类型,既能实现程序功能,也能提高执行

效率和内存使用效率。

4. 数据类型之间的转换

在 Java 中,简单类型数据间的转换,有两种方式:

自动转换和强制转换,通常发生在表达式中或方法的参数传递时。

4.1. 自动转换

具体地讲,当一个较"小"数据与一个较"大"的数据一起运算时,系统将自动将"小"数据转换成"大"数据,再进行运算。

而在方法调用时,实际参数较"小",而被调用的方法的形式参数数据又较"大"时(若有匹配的,当然会直接调用匹配的方

法),系统也将自动将"小"数据转换成"大"数据,再进行方法的调用,自然,对于多个同名的重载方法,会转换成最"接

近"的"大"数据并进行调用。

这些类型由"小"到"大"分别为  (byte,short,char)--int--long--float—double。

这里我们所说的"大"与"小",并不是指占用字节的多少,而是指表示值的范围的大小。

1、下面的语句可以在Java中直接通过:

byte b;

int i=b; 

long l=b; 

float f=b; 

double d=b;

2、如果低级类型为char型,向高级类型(整型)转换时,会转换为对应ASCII码值,例如

char c='c'; int i=c;

System.out.println("output:"+i);

输出:output:99;

3、对于byte,short,char三种类型而言,他们是平级的,因此不能相互自动转换,可以使用下述的强制类型转换。

short i=99 ; 

char c=(char)i; 

System.out.println("output:"+c);

输出:output:c;

4.2. 强制转换

4.2.1. 将"大"数据转换为"小"数据时,你可以使用强制类型转换

即你必须采用下面这种语句格式:

  • int n=(int)3.14159/2;可以想象,这种转换肯定可能会导致溢出或精度的下降。
4.2.2. 表达式的数据类型自动提升, 关于类型的自动提升,注意下面的规则

① 所有的byte,short,char型的值将被提升为int型;

② 如果有一个操作数是long型,计算结果是long型;

③ 如果有一个操作数是float型,计算结果是float型;

④ 如果有一个操作数是double型,计算结果是double型;

例, byte b; b=3; b=(byte)(b*3);//必须声明byte。

4.2.3. 包装类过渡类型转换

一般情况下,我们首先声明一个变量,然后生成一个对应的包装类,就可以利用包装类的各种方法进行类型转换

了。例如:

① 当希望把float型转换为double型时:

float f1=100.00f;

Float F1=new Float(f1);

double d1=F1.doubleValue();//F1.doubleValue()为Float类的返回double值型的方法

② 当希望把double型转换为int型时:

double d1=100.00;

Double D1=new Double(d1);

int i1=D1.intValue();

简单类型的变量转换为相应的包装类,可以利用包装类的构造函数。

即:Boolean(boolean value)、Character(char value)、Integer(int value)、Long(long  value)、

Float(float value)、Double(double value)

而在各个包装类中,总有形为××Value()的方法,来得到其对应的简单类型数据。

利用这种方法,也可以实现不同数值型变量间的转换,例如,对于一个双精度实型类,intValue()可以得到其对应

的整型变量,而doubleValue()可以得到其对应的双精度实型变量。

4.2.4. 字符串与其它类型间的转换

其它类型向字符串的转换

① 调用类的串转换方法:X.toString();

② 自动转换:X+"";

③ 使用String的方法:String.volueOf(X);

4.2.5. 字符串作为值,向其它类型的转换

① 先转换成相应的封装器实例,再调用对应的方法转换成其它类型

例如,字符中"32.1"转换double型的值的格式为:new Float("32.1").doubleValue()。

也可以用:Double.valueOf("32.1").doubleValue()

② 静态parseXXX方法

String s = "1";

byte b = Byte.parseByte( s );

short t = Short.parseShort( s );

int i = Integer.parseInt( s );

long l = Long.parseLong( s );

Float f = Float.parseFloat( s );

Double d = Double.parseDouble( s );

③ Character的getNumericValue(char ch)方法

4.2.6. Date类与其它数据类型的相互转换

整型和Date类之间并不存在直接的对应关系,只是你可以使用int型为分别表示年、月、日、时、分、秒,这样就

在两者之间建立了一个对应关系,在作这种转换时,你可以使用Date类构造函数的三种形式:

① Date(int year, int month, int date):以int型表示年、月、日

② Date(int year, int month, int date, int hrs, int min):以int型表示年、月、日、时、分

③ Date(int year, int month, int date, int hrs, int min, int sec):以int型表示年、月、日、时、分、秒

在长整型和Date类之间有一个很有趣的对应关系,就是将一个时间表示为距离格林尼治标准时间1970年1月1日0

时0分0秒的毫秒数。

对于这种对应关系,Date类也有其相应的构造函数:Date(long date)。

获取Date类中的年、月、日、时、分、秒以及星期你可以使用Date类的getYear()、getMonth()、getDate()、

getHours()、getMinutes()、getSeconds()、getDay()方法,你也可以将其理解为将Date类转换成int。

而Date类的getTime()方法可以得到我们前面所说的一个时间对应的长整型数,与包装类一样,Date类也有一个

toString()方法可以将其转换为String类。

有时我们希望得到Date的特定格式,例如20020324,我们可以使用以下方法,首先在文件开始引入,

import java.text.SimpleDateFormat;
import java.util.*;

java.util.Date date = new java.util.Date();

//如果希望得到YYYYMMDD的格式
SimpleDateFormat sy1=new SimpleDateFormat("yyyyMMDD");
String dateFormat=sy1.format(date);
  
//如果希望分开得到年,月,日
SimpleDateFormat sy=new SimpleDateFormat("yyyy");
SimpleDateFormat sm=new SimpleDateFormat("MM");
SimpleDateFormat sd=new SimpleDateFormat("dd");
String syear=sy.format(date);
String smon=sm.format(date);
String sday=sd.format(date);

4.3. 其他

4.3.1. byte、short、int、long、float、double

当将较小的数据类型转换为较大的数据类型时,Java 编译器会自动完成类型转换,称为“自动类型提升”或“自

动拓宽转换”。

例如:

byte a = 123;
int b = a; // 自动类型提升为 int 类型
4.3.2. char

char 类型和其它数据类型之间的转换需要进行显示转换,称为“强制类型转换”或“强制截断转换”。

例如:

char c = 'A';
int d = (int) c; // 强制将 char 类型转换为 int 类型
4.3.3. boolean

boolean 类型无法与其它数据类型进行自动类型转换,只能用一个 boolean 类型值去赋给另一个 boolean 类型

变量。例如:

boolean e = true;
boolean f = e; // 直接赋值,无需进行类型转换
4.3.4. 总结

在 Java 中存在自动类型转换和强制类型转换两种类型转换方式,

自动类型转换是在类型兼容的情况下自动进行的。

类型提升规则:将较小类型的值(如 byte、short 或 char)提升为 int;

如果有一个操作数是 long 类型,就把另一个操作数隐式地转换为 long 类型。

如果有一个操作数是 float 类型,就把另一个操作数隐式地转换为 float 类型。

如果有一个操作数是 double 类型,就把另一个操作数隐式地转换为 double 类型。

4.4. 总结

只有boolean不参与数据类型的转换

1、自动类型的转换

a. 常数在表数范围内是能够自动类型转换的

b. 数据范围小的能够自动数据类型大的转换(注意特例)

int到float,long到float,long到double 是不会自动转换的,不然将会丢失精度

2、强制类型转换:用圆括号括起来目标类型,置于变量前

四、变量

1. 简介

变量就是在程序运行过程中,其值可以发生改变的量,也可以说变量就是存储一个数据的内存区域,且里面存储的

数据可以变化。

在 Java 中,变量定义格式为:数据类型 变量名 = 值;

例如:

int x = 10;
String name = "John";
double salary = 10000.0;

此处我们使用了整型、字符串和双精度浮点型数据类型来定义变量,并且通过赋值符号“=”给变量赋值。

2. 变量名命名规则

在 Java 中,变量名是用来标识变量的名称。为了使程序易于理解和阅读,Java 有一些变量命名规则需遵守:

  • 变量名必须以字母(A~Z 或 a~z)或下划线(_)开头。
  • 变量名不能以数字开头。
  • 变量名只能由字母、数字、下划线或美元符号($)组成。
  • 变量名区分大小写。
  • 变量名应该具有描述性,以便于代码的阅读和理解。
  • 变量名不应该与 Java 的关键字或保留字重复。

例如,以下是有效的变量名:

playerName
city_name
maxCount
_totalAmount

而以下是无效的变量名:

123player // 数字不能作为变量名的开头
my-name // 减号不能用于变量名
class // 不能用关键字作为变量名

3. 变量类型

在 Java 中,变量的类型决定了变量可以存储哪些类型的数据以及占用多少内存空间。

Java 中的数据类型可以分为两类:原始数据类型和引用数据类型。

3.1. 原始数据类型

Java 中的原始数据类型包括八种基本类型:boolean、byte、short、int、long、float、double 和 char。

它们分别占用不同大小的内存,如下所示:

数据类型

大小(字节)

范围

boolean

1

true 或 false

byte

1

-128 到 127

short

2

-32768 到 32767

int

4

-2147483648 到 2147483647

long

8

-9223372036854775808 到 9223372036854775807

float

4

有效位数为 6~7 位

double

8

有效位数为 15 位

char

2

Unicode 字符

3.2. 引用数据类型

除了原始数据类型外,Java 还有引用数据类型。这些类型不存储实际的数据,而是存储对象的引用或地址。

Java 中的引用数据类型包括类、数组、接口和枚举等。

例如,以下是一些常见的引用数据类型:

String str = "Hello, world!"; // 字符串
int[] arr = new int[10]; // 整型数组
List<Integer> list = new ArrayList<>(); // 列表
Map<String, Object> map = new HashMap<>(); // 映射表

在这些例子中,str 是一个字符串对象的引用,arr 是一个整型数组对象的引用,

list 是一个列表对象的引用,map 是一个映射表对象的引用。

4. 变量的作用域

① 变量可以被访问到的范围。

② 只有在作用域范围内变量才可以被使用。

③ 按作用域来分,变量可以分为:局部变量、全局变量、类变量及方法参数变量。

全局变量可以在整个文件甚至整个程序中都能被访问到。

局部变量在方法或方法代码中声明的变量,作用域为它所在的代码块(整个方法或方法中的某块代码)。

类变量在类中声明,而不是在类的某个方法中声明,它的作用域是整个类。

方法参数的作用域就是当前方法,与局部变量类似。

5. 变量声明格式

多个变量间用逗号隔开,例如:

int a, b, c;
double d1, d2=0. 0;
float e = 2.718281828f;

6. 成员变量和局部变量的区别

7. 总结

在 Java 中,变量名需要遵循一定的命名规则,以提高代码的可读性和可维护性。

最好给变量取一个有意义的名字,可以从变量名中推断出变量的类型和用途。

变量的类型决定了变量可以存储哪些类型的数据以及占用多少内存空间。

Java 中的数据类型分为原始数据类型和引用数据类型两类,每个类型都有其特定的属性和方法。

五、常量

在 Java 中,常量是一种特殊的变量,其值不能被修改。

在声明常量时,需要使用 final 关键字来修饰变量。

Java 中的常量可以分为两类:字面常量和符号常量。

1. 字面常量

字面常量是指直接出现在代码中并且没有名字的常量。例如:

int num = 10; // 整型字面常量,值为 10
char ch = 'A'; // 字符字面常量,值为 A
String str = "Hello, world!"; // 字符串字面常量,值为 Hello, world!
boolean flag = true; // 布尔字面常量,值为 true

2. 符号常量

符号常量是用标识符来表示的常量,在程序中只能被赋值一次,其值不能被更改。

在定义符号常量时,通常使用 static 和 final 关键字来定义。

例如:

public static final double PI = 3.1415926;

在这个例子中,我们定义了一个名为 PI 的符号常量,并使用 static 和 final 关键字来保证它的唯一性和不可变

性。通常情况下,我们将符号常量放置在一个单独的接口或者类中进行管理。

3. 字符型常量和字符串常量之间的区别

3.1. 数据类型

字符型常量(Character literal)是指单个字符,使用单引号(')括起来表示。例如:'a'、'b'、'1'、'$' 等。

而字符串常量(String literal)则是由多个字符组成的序列,使用双引号(")括起来表示。

例如:"Hello"、"Java"、"123" 等。

3.2. 存储方式

字符型常量只占用一个字节的存储空间,可以存储在 char 类型变量中。

而字符串常量占用多个字节的存储空间,需要使用 String 类型或其他相关类型来进行存储。

3.3. 使用场景

字符型常量通常用于表示单个字符或字符的 ASCII 码值,可以在 char 类型变量中进行存储和操作。

例如, switch 语句中的 case 标签、char 类型的数据处理等情况下都会涉及到字符型常量。

字符串常量则通常用于表示文本或长字符串,可以使用 String 类型进行存储和操作。

例如,处理输入输出、记录错误信息等都可以使用字符串常量。

3.4. 转义字符

在字符型常量中可以使用一些特殊的字符表示为转义字符,例如 '\n' 表示换行符、'\t' 表示制表符等。

而在字符串常量中也可以使用这些转义字符,例如 "Hello\nJava" 表示字符串 "Hello" 和 "Java" 之间有一个换行

符。

3.5. 总结

字符型常量和字符串常量都是 Java 中常见的常量类型,它们在数据类型、存储方式、使用场景和转义字符等方面

存在差别。

了解它们之间的区别对于编写 Java 程序非常重要。

在 Java 中,常量是一种特殊的变量,其值不能被修改。

Java 中的常量可以分为字面常量和符号常量两类。

在声明符号常量时,需要使用 static 和 final 关键字来修饰变量,在程序中只能被赋值一次,其值不能被更改。

六、字面量

字面量是 Java 中一种用于表示数据值的常量表达式,可以直接使用而不需要声明。

字面量可以是数字、字符、字符串或布尔值。

以下是一些常见的字面量类型:

1. 整型字面量

整型字面量可以是十进制、八进制、十六进制等形式。例如:

int num1 = 10; // 十进制表示法
int num2 = 010; // 八进制表示法,等价于十进制数 8
int num3 = 0x10; // 十六进制表示法,等价于十进制数 16

2. 浮点型字面量

浮点型字面量包括单精度浮点数和双精度浮点数两种形式,分别用 f 或 F 和 d 或 D 表示。例如:

float fnum = 3.14f; // 单精度浮点数
double dnum = 3.1415926; // 双精度浮点数

3. 字符型字面量

字符型字面量是用单引号括起来的一个字符,例如:

char ch1 = 'A'; // 字符 A
char ch2 = '\n'; // 换行符
char ch3 = '\u03C0'; // Unicode 字符 PI(π)

4. 字符串字面量

字符串字面量是用双引号括起来的一组字符,例如:

String str1 = "Hello, world!"; // 字符串
String str2 = ""; // 空字符串
String str3 = "多\n行\n文\n本"; // 多行字符串

5. 布尔型字面量

布尔型字面量只有两个值:true 和 false,例如:

boolean flag1 = true; // 布尔型字面量 true
boolean flag2 = false; // 布尔型字面量 false

6. 总结

在 Java 中,字面量指的是直接出现在程序中的常量值。

Java 中的字面量可以是数字、字符、字符串或布尔值。

不同类型的字面量在语法上有所不同,但它们都能被编译器解析为相应的数据类型。

七、方法

1. 简介

方法就是可以把一段代码封装成一个功能,以方便重复调用的一种语法结构,

方法的存在目的就是为了提高了代码的复用性且让程序的逻辑更清晰

有返回值的方法调用时可以选择定义变量接收结果,或者直接输出

无返回值方法的调用只能直接调用且不能使用return返回数据,但是可以直接return;

2. 方法定义

3. 方法的标识

方法的唯一标识就是: 方法的名字 和 参数列表

一个类中不能出现两个方法的标识完全一样的方法

4. 方法的参数传递机制

4.1. 基本类型的参数传递

基本类型的参数传递即值传递,也就是在传输实参给方法的形参的时候,并不是传输实参变量本身,而是传输实参变

量中存储的值!

值传递:是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响

到实际参数

注意:Java语言只有值传递

Java 语言只有值传递,是因为 Java 中变量的赋值本质上都是将变量的值复制给另一个变量或方法参数,而不是将

变量本身传递。

在 Java 中,基本数据类型(如 int、double、boolean 等)属于值类型,它们的值存储在变量中。当将一个值类

型的变量作为参数传递给一个方法时,实际上传递的是该变量所包含的值的副本,而不是变量本身。这样,在方

法中对该参数的修改不会影响原来的变量值。

而引用类型(如数组、对象等)属于指针类型,在变量中保存的是对象的引用,而不是对象本身。当将一个引用

类型的变量作为参数传递给一个方法时,实际上传递的是该变量所包含的引用值的副本,而不是对象本身。这

样,在方法中对该参数所引用的对象的修改会反映到调用者的变量中,因为它们引用同一个对象。

总之,Java 语言只有值传递,因为变量的赋值和参数传递都是针对变量的值进行操作的,基本数据类型和引用类

型的参数传递方式存在差异。在使用方法时需要注意对参数的修改是否会影响到原始变量值。

4.2. 引用类型的参数传递

引用类型的参数传递即地址传递,也就是传递堆内存的地址,就如传递数组、字符串、对象类型等

复制的是参数的引用(地址值),并不是引用指向的存在于堆内存中的实际对象

引用传递:是指在调用函数时将实际参数的地址传递到函数中,那么在函数中对参数所进行的修改,将影响到实

际参数

5. 方法重载 & 重写

5.1. 方法重载

方法重载(Method Overloading)是指在同一个类中定义多个方法名称相同但参数列表不同的方法。

重载的方法必须满足两个条件:

  • 方法名称必须相同。
  • 参数类型或个数必须不同。

例如,在一个类中可以定义多个名为 add 的方法,例如:

public int add(int a, int b) {
    return a + b;
}

public float add(float a, float b) {
    return a + b;
}

public double add(double a, double b, double c) {
    return a + b + c;
}

其中,第一个方法接收两个 int 类型的参数,返回它们的和;第二个方法接收两个 float 类型的参数,返回它们的

和;第三个方法接收三个 double 类型的参数,返回它们的和。

当调用这些方法时,Java 根据传递的参数类型或个数来自动选择匹配的方法。例如:

int x = add(2, 3);          // 调用 add(int a, int b) 方法,返回 5
float y = add(2.0f, 3.0f);  // 调用 add(float a, float b) 方法,返回 5.0f
double z = add(2.0, 3.0, 4.0);  // 调用 add(double a, double b, double c) 方法,返回 9.0

需要注意的是,方法重载与返回值类型无关。也就是说,不能仅根据方法的返回值类型来重载方法。

总之,方法重载是 Java 中一种常见的编程技巧,它可以提高代码的灵活性和可读性,避免在不同场景下重复定义

类似但参数有所不同的方法。

5.2. 方法重写

方法重写(Method Overriding)是指在子类中重新定义或实现从父类继承而来的方法,使得子类可以根据自己

的需要对父类方法进行更改或扩展。

在 Java 中,方法重写需要满足以下三个条件:

  • 方法名称、参数列表和返回类型必须与父类中被重写的方法完全相同。
  • 访问修饰符不能缩小,也就是不能从 protected 和 public 缩小到 private 或默认。
  • 子类方法抛出的异常类型必须与父类方法声明的异常类型相同或是其子集。

例如,假设有一个父类 Animal:

public class Animal {
    public void eat() {
        System.out.println("Animal is eating.");
    }
}

现在,我们想在子类 Cat 中重写 eat() 方法。只需要在 Cat 类中定义一个与父类 eat() 方法名称、参数列表和返回

类型都相同的方法即可:

public class Cat extends Animal {
    @Override
    public void eat() {
        System.out.println("Cat is eating fish.");
    }
}

此时,在调用 Cat 对象的 eat() 方法时,会自动调用 Cat 类中的 eat() 方法而非 Animal 类中的 eat() 方法。如果

没有使用 @Override 注 解,则不是真正的方法重写,而仅仅是在子类中新增了一个与父类同名的方法。

总之,方法重写是 Java 面向对象编程中一种常见的技巧,它可以通过在子类中重写父类方法实现对继承方法的扩

展和优化,同时也是实现多态性的重要手段。

5.3. 两者区别

重载(Overload)和重写(Override)都是 Java 中常用的程序设计概念,它们在以下几个方面存在差别:

定义

重载是指在同一个类中定义多个方法名称相同但参数类型或个数不同的方法。

例如:在一个类中可以定义 add(int a, int b)、add(int a, int b, int c) 等方法。

重写是指在子类中重新定义或实现从父类继承而来的方法,方法名称、参数列表和返回类型必须与父类中的方法

完全相同。

例如,在子类中重写 Object 类的 toString() 方法。

程序调用

重载的方法根据调用时提供的参数类型或个数的不同,自动选择调用对应的方法。

重写的方法则是根据调用对象所属类的类型决定调用哪个方法,如果子类中没有重写父类方法,则会自动调用父

类的方法。

目的和作用

重载的目的是提供更丰富的语义表达和更灵活的接口设计,可以让同名方法根据参数的不同而具有不同的行为。

同时也可以避免代码中出现大量相似但参数类型/个数不同的方法。

重写的目的是实现父类对某个方法的抽象,通过子类进行具体实现。通过重写,可以让不同的对象调用相同的方

法,其行为具有多态性。

使用场景

重载通常用于实现不同的功能或参数类型/个数的兼容性处理,例如 Arrays 类中提供了多个 sort() 方法,可以按

照不同的数据类型进行排序。

而重写则通常用于实现子类对父类继承方法的自定义实现,例如在继承 JFrame 类时重写其 paint() 方法以达到自

定义窗口效果。

5.4. 总结

总之,重载和重写都是 Java 中常用的程序设计概念,但它们在定义、程序调用、目的和使用场景等方面存在差

别。了解它们之间的区别可以更好地理解 Java 程序设计中的方法和继承机制。

八、构造器

1. 简介

构造器(Constructor)是 Java 中一种特殊的方法,用于创建对象并初始化对象的状态。

简单来说,构造方法就是用于初始化一个类的对象,并返回对象的地址。

在使用构造方法时需要注意以下几个问题:

  • 构造方法名称必须与类名相同,并且没有返回类型。例如,如果类名为 Person,则其构造方法名称应该是 Person()。
  • 如果没有显式定义构造方法,则 Java 会自动生成一个默认的无参构造方法。但是,如果已经定义了一个或多个构造方法,则 Java 不
    会再自动添加默认构造方法了。
  • 构造方法可以重载,也就是可以定义多个构造方法,只要它们的参数列表不同即可。这样,我们就可以根据不同场景的需求来调用不
    同的构造方法。
  • 构造方法可以使用访问修饰符(public、protected、private)来限制其访问权限。例如,如果将构造方法设置为 private,则该类的
    实例化过程就只能在类的内部进行了,外部访问者无法创建该类的实例。

除此之外,还有一些其他需要注意的细节问题:

  • 通过 super() 调用父类的构造方法必须放在子类构造方法的第一行。
  • 如果子类中定义了构造方法,则编译器不会自动调用父类的无参构造方法。因此,在子类构造方法中必须手动调用父类的构造方法
    (使用 super() 调用父类的构造方法)。
  • 构造方法不能被继承,也就是说,子类无法继承父类的构造方法。但是,子类可以通过调用 super() 来访问父类的构造方法。
  • 构造方法不能被重写,因为重写的方法名称、参数列表和返回类型必须与父类完全相同,而构造方法名称不能与类名不同。

总之,在编写 Java 构造方法时需要注意这些问题,以确保程序能够正常运行并满足需求。

2. 格式

修饰符 类名(形参列表){
	. . .
}

例如:在 new Person()中执行Person的构造方法

初始化对象格式:类名 对象名称 = new 构造器;

3. 作用

能够创建一个具备初始属性的对象,给对象属性进行初始化的

4. 分类

4.1. 无参构造器

无参数构造器(默认存在):初始化对象时,成员变量的数据均采用默认值初始化

4.2. 有参构造器

有参数构造器:初始化对象时,同时可以为对象进行赋值

5. 规则

任何类定义出来, 默认都自带无参数构造器

一旦定义有参数构造器, 无参数构造器就不再提供

6. 构造器 Constructor 是否可被 override

构造器 (Constructor) 不可以被覆盖(override)。当在子类中定义一个与父类相同的构造器时,实际上是对它进行

了重载(overload),而不是覆盖。

当使用子类对象创建实例时,首先会调用父类的构造器,然后再调用子类的构造器。如果子类没有显示地调用父

类的构造器,则默认会调用父类的无参数构造器。因此,在子类中定义一个与父类相同的构造器可能会导致子类

无法正确地初始化父类中的成员变量或方法,从而引发一些问题。

但是,可以在子类构造器中使用 super 关键字来调用父类的构造器,并且可以使用 super() 调用父类的无参数构

造器,也可以使用super(parameters) 来调用父类的有参数构造器。

总之,构造器不可被 override,但可以在子类中使用 super 调用父类构造器来初始化父类中的成员变量和方法。

  • 22
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值