Java

八大基本类型

Java 是强类型语言,每个变量必须声明一种数据类型。

JavaScript是弱类型语言,变量定义可以不声明数据类型。

Java共有八种基本类型(primitive type):包含四种整型、两种浮点型、一种字符型、以及一种布尔型

关于 bit byte kb mb gb tb

bit 代表一位,比如二进制数据 1101 就是 4 位

byte 代表一个字节, 1 byte = 8 bit , 二进制数 1101 0011 就是一个字节

kb 1kb = 1024 byte

mb 1mb = 1024 kb

整型

类型字节数位数最小值最大值默认值byte1byte8bit-2^72^7-10short2byte16bit-2^152^15-10int4byte32bit-2^312^31-10long8byte64bit-2^632^63-10

long 需要在数字后加上 L/l

8bit 的最大值和最小值为什么是 2^7 – 1 和 -2^7 ?

一个八位的二进制数,可以表示2 ^ 8 个数字,其中正负各占一半。

整型各个进制表示法

八进制:0 **;(*表示0到7任意数字)

十六进制:0x** _;(表示0到f任意数字)

Java 7 新特性:

二进制0b_** _;(_ 表示0或1)

Java7 新特性 :

int a = 100_345_001; 可以使用下划线分割数字,编译时自动忽略下划线,等同于100345001 。 作用,便于阅读

浮点型

类型字节数位数默认值float4byte32bit0.0double8byte64bit0.0

float需要在数字后加上 F/f, 如果不加代表double双精度浮点类型

浮点类型不适用于禁止出现舍入误差的金融计算中 (2.0-1.1)= 0.8999999999

浮点类型的运算遵循 IEEE754 标准,即IEEE二进制浮点数算术标准。

在Java1.2时,提出了关键字 strictfp ,用来修饰方法和类型, 被该关键字修饰后,会完全遵循 IEEE754标准进行浮点运算,早期java的浮点运算是一种近乎我行我素的方式。

char类型

char类型用于表示单个字符,通常用于表示字符常量,两个字节,16bit, 他的默认值为 0 ,即字符 空格。

可以表示:

  • 单个ASCII字符: ‘A’ 即 65

  • Unicode编码单元:十六进制值,范围为\u0000\uffff

  • 转义字符:\b, , , , \", \', \\

代码点和代码单元

背景

Unicode是为了统一世界上所有符号所设置的编码方式,当时认为 unicode使用2个字节表示足以应对所有的文字和符号,但是,随着时间的发展,16位的unicode编码无法表示所有的字符了。

概念

代码点 code point,是指一个编码表中某个字符对应的代码值。在Unicode编码表中,一个代码点使用十六进制数表示,并且加上前缀 U+, 比如 U+0041 是字母 A 的代码点, U+2A000 是增补字符 𪀀 的代码点。

Unicode 代码点,可以分为17个代码级别(code plane)。第一个代码级别称为 基本的多语言级别(basic multilingual plane),代码点从 U+0000U+FFFF,主要包含了经典的Unicode代码点。其余的16个附加级别, 代码点从 U+10000U+10FFFF,包含了一些增补字符

代码单元 code unit,utf-16编码使用两个字节表示一个基本语言级别的字符,及一个代码单元,比如 \u20041。 当遇到增补字符时,16位无法进行表示,则使用两个代码单元表示,比如汉字 𪀀 的字符编码为 \ud868\udc00

在 Java 中char类型用UTF-16编码描述一个代码单元。

注意: 增补字符无法用一个char来表示,比如: char a = '\ud868\udc00'; 就会报错

boolean类型

默认值 false,占用字节大小要根据虚拟机确定。

整型和布尔类型之间不能相互转换。

有关数据类型的面试题

short和int的转换问题

byte b = 128;  // 打印b?:A编译报错 B运行报错 C:可以输出
// 选A

short s = 1;
s = s + 1; // 打印s?:A编译报错 B运行报错 Cs=2
// 选A,类型不匹配
// s + 1 , 因为1是int类型,会自动提升表达式类型为int,而接收的是short,所以编译报错

s += 1; // 正确?
// 正确,正常执行,+=是语法规定的特殊运算符,有对其特殊处理,所以正常执行

char类型是否可以存储一个汉字?

char 存储一个Unicode代码单元,汉字则是由一个Unicode代码点组成,一个代码点可能由多个代码单元组成。

所以char可以存储一个汉字,但是生僻字就不行了。

double 和 float

double a = 100f;  // 正确,小范围类型数据可以赋值给大范围类型,即向上转型,直接自动转换
float b = 100; // 编译报错,100 是一个double类型,大不可给小,会有精度损失,即向下转型,需要强制类型转换帮助

double 和 long

double a = 100L; // 正确
long b = 100.0; // 编译报错,double比long更精确
long b = 100.0f; // 同样不可以

浮点运损失

3 * 0.1 == 0.3  // false 浮点运算都有损失

包装类型与基本类型比较

Double d1 = 100.0;
Double d2 = 100.0;
d1 == d2; // false d1 d2 都是包装类型,都是被自动装箱创建出来的

Integer i1 = 128;
Integer i2 = 128;
i1 == i2  // false,同上

Integer i3 = 127;
Integer i4 = 127;
i3 == i4 // true,Integer内部有IntegerCache,是一个享元池,范围在-128~127之间的值,自动装箱时直接从池中获取,提高了java性能
// Short/Byte/Long同样具有范围也是(-128~127)
// 浮点数没有

new Long(100L) == 100L; // true , 基本类型和包装类型比较时,先将包装类型拆箱后再比较

new Long(100).equals(100L); // true
new Long(100).equals(100); // false
// 先比较类型,再比较值

Java中的i++和++i

参考:

i = i++

int i = 0;
i = i++; // 0

解释

Java 使用缓存变量的机制, i=i++ 相当于:

int i = 0;
temp = i;
i++; // i = i + 1;
i = temp;

i++ 和 ++i 多个混合运算


int i=5;
int s=(i++)+(++i)+(i--)+(--i)=24;
解析:
1. i++ => i=6; s'=5;
2. ++i => i=7; s'=7;
3. i-- =>  i=6; s'=7;
4. --i =>  i=5; s'=5;
==>s=5+7+7+5=24;

金山面试

file

答案

i = 2;
j = 7;
k = 7;
h =3;
p1 = 1;
p2 = 1;
q1 = 1;
q2 = 0;

for、foreach、iterator遍历时修改、删除、增加的问题

Java中,一般存在三种遍历方式:

  • for

  • foreach

  • iterator

for循环

  • 修改:遍历中修改,不会存在任何问题

  • 删除:遍历中删除某个元素,集合/数组的长度就会变短,这样在遍历时就有可能造成数组下标越界异常(注意,for循环的list.size每次遍历都会获取,所以如果删除一个中间元素,遍历并不会出现下标越界;下表越界只会发生在同一循环内,比如已经删除了最后一个元素,却又获取最后一个元素的情况)

    // 代码示例如下
    // 不会抛出异常
    for (int i = 0; i < list.size(); i++) {
    if (i == 0) {
        list.remove(0);
    }
    System.out.println(list.get(i));
    }
    // 会抛出数组下标越界
    for (int i = 0; i < list.size(); i++) {
    if (i == 2) {
        list.remove(0);
    }
    System.out.println(list.get(i));
    }
  • 添加:遍历中一直添加元素,有可能造成无穷循环的情况

    // 代码示例如下
    for (int i = 0; i < list.size(); i++) {
        if (i == list.size() - 1) {
            list.add("a");
        }
    }

foreach 与 iterator

foreach 实际上只是 iterator的一个语法糖,如果由如下foreach代码:

for (String item : items) {
    System.out.print(item);
}

实际上编译后的代码为:

Iterator<String> iterator = items.iterator();
while (iterator.hasNext()) {
     System.out.print(iterator.next());
}
  • 修改:使用iter在遍历时修改,与for循环一样,不会发生任何问题

  • 删除:使用iter在遍历时删除元素,如果使用的是 iterator.remove() 方法是可以正常删除的:

    // 情况一: 使用集合提供的remove方法:
    for (String s : list) {  // 抛出ConcurrentModificationException 抛出并发修改异常,无法删除
        if (s.equals("0")) {
            list.remove(0);
        }
        System.out.println(s);
    }
    // 情况二:删除倒数第二个元素,不会报错,因为下次hasNext返回false直接退出循环
    Iterator<string> iterator = list.iterator();
    while (iterator.hasNext()) {
        String value = iterator.next();
        if ("1".equals(value)) {
            list.remove(list.size() - 2); // 删除元素1
        }
        System.out.println(value); // 0, 1
    }
    System.out.println(list); // [0, 2]
    // 情况三:使用iterator提供的remove方法:
    Iterator<string> iterator = list.iterator();
    while (iterator.hasNext()) {
        String value = iterator.next();
        if ("0".equals(value)) {
            iterator.remove(); // 不会抛出异常,会正常删除
        }
        System.out.println(value); // 还是会打印出被删除的元素  0,1,2
    }
    System.out.println(list); // 输出的元素确实是删除过后的 [1,2]</string></string>
  • 增加:增加无论任何情况下,都会抛出 ConcurrentModificationException 异常

Iterator的源码

参考类: ArrayList$Itr

// 源码如下:
private class Itr implements Iterator<E> {
    int cursor;       // 下次调用next()获取的元素的指针
    int lastRet = -1; // 最后一次操作的索引,如果上次操作为remove,则会置为-1  是一个记录变量
    int expectedModCount = modCount; // modCount代表当前列表被修改的次数,使用expectedModCount记录当前状态的修改次数

    public boolean hasNext() { // 如果当前指针大小小于list的size大小,说明还有元素没有遍历完
        return cursor != size;
    }

    // 获取下一个元素
    @SuppressWarnings("unchecked")
    public E next() {
        checkForComodification();
        int i = cursor;
        // 判断是否已经遍历到最后一个元素
        if (i >= size)
            throw new NoSuchElementException();
        Object[] elementData = ArrayList.this.elementData;
        if (i >= elementData.length)
            throw new ConcurrentModificationException();
        cursor = i + 1; // 获取cursor位置元素成功,将指针位置后移一位(cursor++)
        return (E) elementData[lastRet = i]; // 使用lastRet记录上一次操作的位置
    }

    // 移除当前元素
    public void remove() {
        // 如果上次操作为remove,则抛出非法参数异常,所以循环直接使用iterator.remove() 就会抛出 IllegalStateException
        if (lastRet < 0)
            throw new IllegalStateException();
        checkForComodification();

        try {
            // 调用当前集合的remove()方法,移除当前的元素
            ArrayList.this.remove(lastRet);
            cursor = lastRet; // 指针变为上次操作的索引(删除,数组后面的元素会向前移动,下次遍历的元素位置是coursor+1)
            lastRet = -1; // 标记上次操作为remove
            expectedModCount = modCount; // 更新当前修改次数
        } catch (IndexOutOfBoundsException ex) {
            throw new ConcurrentModificationException();
        }
    }

    @Override
    @SuppressWarnings("unchecked")
    public void forEachRemaining(Consumer<? super E> consumer) {
        Objects.requireNonNull(consumer);
        final int size = ArrayList.this.size;
        int i = cursor;
        if (i >= size) {
            return;
        }
        final Object[] elementData = ArrayList.this.elementData;
        if (i >= elementData.length) {
            throw new ConcurrentModificationException();
        }
        while (i != size && modCount == expectedModCount) {
            consumer.accept((E) elementData[i++]);
        }
        // update once at end of iteration to reduce heap write traffic
        cursor = i;
        lastRet = i - 1;
        checkForComodification();
    }

    // 检查修改次数是否发生变化: 即检测list是否被修改
    final void checkForComodification() {
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
    }
}

按位运算符

按位&

非短路与,所有的表达式都会执行,最主要的功能不是用来连接判断条件表达式的.

表达式1 & 表达式2 & 表达式3 ....

当前仅当所有表达式为true的时候,整个判断结果才是true. 最主要的是具备运算能力.

当且仅当全部是1的情况,结果才是1.
                  10&8=8
                  1 0 1 0
                  1 0 0 0
                  -------
                  1 0 0 0->十进制8

按位 |

非短路或,所有的表达式都会执行,最主要的功能不是用来连接判断条件表达式的.

表达式1 | 表达式2 | 表达式3 ....

只要有一个表达式是true,那么结果就是true:, 最主要的是具备运算能力

只要出现一个1,就是1
                  10|8=8
                  1 0 1 0
                  1 0 0 0
                  -------
                  1 0 1 0->十进制10

取反

1变0,0变1

            ~10
            00000000 00000000 00000000 00001010
            11111111 11111111 11111111 11110101

异或. ^,相同为0,不同为1

        10^8^8 = 10
                1 0 1 0
                1 0 0 0
                --------
                0 0 1 0 -> 2
                1 0 0 0
                -------
                1 0 1 0 -> 10

结论:一个数连续异或同一个数两次,结果是它本身. 应用:以后可以利用^进行加密和解密[IO流...]

位移运算符

面试:位移运算是计算机中性能最高的运算方式,请您以最高性能的方式计算出2的3次方

System.out.println(2+2+2+2);
System.out.println(2*2*2);
System.out.println(Math.pow(2,3));
System.out.println(2<<2);

<<:左位移...
2<<2
000000 00000000 00000000 0000001000 -> 8

>>:带符号的右位移,移动了多少位,就在最高位补符号位
>>>:不带符号的右位移,移动了多少位,就在最高位补几个0

01

面向对象基本概念

软件开发主流开发方法:

  1. 结构化开发方法

  2. 面向对象开发方法

结构化程序设计

结构化程序设计:自顶向下、逐步求精、模块化

结构化程序设计步骤

  1. 结构化分析(Structured Analysis,SA):需求分析

  2. 结构化设计(Structured Design,SD):系统概要设计、详细设计

  3. 结构化编码(Structured Program,SP)

主张按照功能将软件系统划分,也称为 面向功能程序设计,每个功能都有输入输出, 函数 就是结构化程序设计的最小单位。

自顶向下:每个模块包含子模块,子模块又包含子模块

逐步求精:先完成大体结构,再具体到小功能(主函数作为入口, 将其他众多函数串起来)

缺点

  1. 不直观,需要将客观世界抽象为具体功能

  2. 扩展性差,修改实现方式,需要自顶向下的修改模块结构

面向对象程序设计

面向对象:使用类、对象、封装、继承、消息等基本概念进行程序设计,根据客观事物进行抽象来构造软件系统。

是面向对象程序设计的最小单位,是具有共同属性方法的一类事物,是对客观事物进行抽象模拟,通过公共方法暴露该类提供的功能,从而提高内聚性,降低耦合,类是由方法和属性组成:

成员变量(状态数据) + 方法 (行为) = 类定义

对象:是类的具体实例,反应客观事物,具有标识唯一性、分类性、多态性、封装性、模块独立等。

消息:对象直接相互通信合作,需要“消息”机制完成

面向对象的三大特征

  1. 封装(Encapsulation)

  2. 继承(Inheritance)

  3. 多态(Polymorphism)

  • 封装 是指将对象的具体细节隐藏起来,使用公共方法暴露对象的功能

  • 继承 是面向对象实现软件复用的手段

  • 多态 则是指子类对象赋值给父类引用,编译时类型为父类,运行时类型为子类,意味着统一类型对象,可能表现多种特征。

抽象 也是面向对象的重要组成部分,但是所有编程语言都需要抽象,抽象只考虑部分可归类的问题,不考虑所有细节。

面向对象软件开发三个阶段

面向对象软件开发,需要经过三个阶段:

  • OOA(面向对象分析):建立分析模型,文档化

  • OOD(面向对象设计):使用面向对象对OOA细化

  • OOP(面向对象编程):实现OOD

最后更新于