16<<2 java_java基础的再理解

本文详细介绍了Java的基础知识,包括自增自减、位移运算符等概念,并深入探讨了String类的特点及使用技巧。此外,还讲解了集合类如List、Set、Map的特性及其不同实现方式。

1、java基础

1.1、自增、自减

++a: 先赋值再运算

a++:先运算再赋值

1.2、位移运算符

首先要计算它的补码,再来位移。

一个数为正,则它的原码、反码、补码相同

一个数为负,则符号位为1,其余各位是对原码取反,然后整个数加1

25 ==> 原码 0001 1001 ==> 反码 0001 1001 -->补码 0001 1001

-25 ==>原码 1001 1001 ==> 反码1110 0110 ==>补码 1110 0111

案例:

<

16<<2

16的二进制原码为 0001 0000

16的二进制反码为 0001 0000

16的二进制补码为 0001 0000

16<<2就是将16的二进制补码左移两位,低位补0,那么得到:

左移两位后的补码为 0100 0000

将得到的补码转换为十进制,那么16<<2的结果为64

-16<<2

-16的二进制原码为 1001 0000

-16的二进制反码为 1110 1111

-16的二进制补码为 1111 0000

左移两位后的补码为1100 0000

左移两位后的反码为1011 1111

左移两位后的原码为1100 0000

将得到的补码转换为十进制,那么-16<<2的结果为-64

表示右移,如果该数为正,则高位补0,若为负数,则高位补1;

16>>2

16的二进制原码为 0001 0000

16的二进制反码为 0001 0000

16的二进制补码为 0001 0000

16>>2就是将16的二进制补码右移两位,高位补0,那么得到:

右移两位后的补码为 0000 0100

将得到的补码转换为十进制,那么16>>2的结果为4

-16>>2

-16的二进制原码为 1001 0000

-16的二进制反码为 1110 1111

-16的二进制补码为 1111 0000

右移两位后的补码为1111 1100

左移两位后的反码为1111 1011

左移两位后的原码为1000 0100

将得到的补码转换为十进制,那么-16>>2的结果为-4

2、集合类

2.1、String

String类是final的,它的所有成员变量也都是final的,为什么是final的?

安全性 :这种类是非常底层的,和操作系统的接触是非常频繁的,如果可以被继承,那么一些人重写了一些方法,往操作系统内部写入一段具有恶意攻击性质的代码什么的,这不就成了核心病毒了么?

效率:设计成final,JVM才不用对相关方法在虚函数表中查询,而直接定位到String类的相关方法上,提高了执行效率

String类其实是通过char数组来保存字符串的

注意:String对象一旦被创建就是固定不变的了,对String对象的任何改变都不影响到原对象,相关的任何change操作都会生成新的对象 (sub操、concat还是replacee操作都不是在原有的字符串上进行的,而是重新生成了一个新的字符串对象)

案例1:

String s1 = "钢联";

String s2 = new String("钢联");

String s3 = s2;

String s4 = "钢联";

String s5 = new String("钢联");

System.out.println(s1 == s2); //false

System.out.println(s2 == s3); //true

System.out.println(s1 == s4); //true

System.out.println(s3 == s4); //false

System.out.println(s1.equals(s2)); //true

System.out.println(s2 == s5); //false

System.out.println(s2.equals(s5)); //true

案例2:

​ String s0="helloworld";

​ String s1="helloworld";

​ String s2="hello"+"world";

​ System.out.println(s0s1); //true 可以看出s0跟s1是指向同一个对象

​ System.out.println(s0s2); //true 可以看出s0跟s2是指向同一个对象

编译器在编译的时候s2就解析成一个字符串常量helloworld

案例3:

​ String s0="钢联";

​ String s1=new String("钢联");

​ String s2="钢" + new String("联");

​ System.out.println( s0s1 ); //false

​ System.out.println( s0s2 ); //false

​ System.out.println( s1==s2 ); //false

案例4:

​ String str1="abc";

​ String str2="def";

​ String str3=str1+str2;

​ System.out.println(str3=="abcdef"); //false

String str3=str1+str2是在运行时刻才能知道的。new对象也是在运行时才做的,如果是String str3=“abc"+"def"则在编译的时候就会解析成字符串常量

案例5:

​ String s0 = "ab";

​ final String s1 = "b";

​ String s2 = "a" + s1;

​ System.out.println((s0 == s2)); // true

对于final修饰的变量,它在编译时被解析成一个常量值的一个本地拷贝存储到自己的常量池中或嵌入到它的字节码流中。

案例6:

public void test10(){

String s0 = "ab";

final String s1 = getS1();

String s2 = "a" + s1;

System.out.println((s0 == s2)); // false

}

private static String getS1() {

return "b";

}

这里面虽然将s1用final修饰了,但是由于其赋值是通过方法调用返回的,那么它的值只能在运行期间确定,因此s0和s2指向的不是同一个对象,故上面程序的结果为false。

案例7:

intern():这是一个native的方法,书上是这样描述它的作用的:如果字符串常量池中已经包含一个等于此String对象的字符串,则返回代表池中这个字符串的String对象;否则,将此String对象包含的字符添加到常量池中,并返回此String对象的引用。

public static void main(String args[]){

String s1=new String("hello1");

System.out.println(s1.intern()s1);//false

System.out.println(s1.intern()"hello1");//true

String s2="hello2";

System.out.println(s2.intern()==s2);//true

}

1、执行intern方法时,如果常量池中存在和String对象相同的字符串,则返回常量池中对应字符串的引用;

2、如果常量池中不存在对应的字符串,则添加该字符串到常量中,并返回字符串引用;

2.2、数组

动态初始化:

创建格式:数组类型[] 数组名 = new 数据类型[数组长度];

静态初始化:

​ 简化格式:

​ 数据类型[] 数组名称 = {值, 值, …};

​ 完整格式(推荐):

​ 数据类型[] 数组名称 = new 数据类型[]{值, 值, …};

通过数组下标索取来取得元素,下标默认从0开始。数组下标超出数组长度,数组越界异常(运行时异常)

数组属于引用数据类型,在使用之前一定要开辟空间(实例化),否则就会产生"NullPoninterException"

2.3集合

Collection 接口的接口 对象的集合(单列集合)

├——-List 接口:元素按进入先后有序保存,可重复

│—————-├ LinkedList 接口实现类, 链表, 插入删除, 没有同步, 线程不安全

│—————-├ ArrayList 接口实现类, 数组, 随机访问, 没有同步, 线程不安全

│—————-└ Vector 接口实现类 数组, 同步, 线程安全

│ ———————-└ Stack 是Vector类的实现类

└——-Set 接口: 仅接收一次,不可重复,并做内部排序

├—————-└HashSet 使用hash表(数组)存储元素

│————————└ LinkedHashSet 链表维护元素的插入次序

└ —————-TreeSet 底层实现为二叉树,元素排好序

Map 接口 键值对的集合 (双列集合)

├———Hashtable 接口实现类, 同步, 线程安全

├———HashMap 接口实现类 ,没有同步, 线程不安全-

│—————–├ LinkedHashMap 双向链表和哈希表实现

│—————–└ WeakHashMap

├ ——–TreeMap 红黑树对所有的key进行排序

└———IdentifyHashMap

2dc5c091e303ed3dafee41174bd106f6.png

Map用于保存具有映射关系的数据,Map里保存着两组数据:key和value,它们都可以使任何引用类型的数据,但key不能重复。所以通过指定的key就可以取出对应的value

6b22aed3d0b4d9b5d079d5d10b2fd3ea.png

ArrayList:

ArrayList是可以动态增长和缩减的索引序列,它是基于数组实现的List类

该类封装了一个动态再分配的Object[]数组,每一个类对象都有一个capacity属性,

表示它们所封装的Object[]数组的长度,当向ArrayList中添加元素时,该属性值会自动增加。

底层的数据结构就是数组,数组元素类型为Object类型,即可以存放所有类型数据。

我们对ArrayList类的实例的所有的操作底层都是基于数组的。

继承AbstractList:接口中全都是抽象的方法,而抽象类中可以有抽象方法,还可以有具体的实现方法

ArrayList实现的接口

List接口

RandomAccess接口:标记性接口,用来快速随机存取,有关效率的问题,在实现了该接口的话,

那么使用普通的for循环来遍历,性能更高,没有实现该接口的话,使用Iterator来迭代如linkedList

Cloneable接口:实现了该接口,就可以使用Object.Clone()方法了。

Serializable接口:实现该序列化接口,表明该类可以被序列化,什么是序列化?

简单的说,就是能够从类变成字节流传输,然后还能从字节流变成原来的类。

ecbd2fa1014792e43f17a606765a4bb5.png

ArrayList异常一:

ArrayList的remove引起的异常

802e369335b0c0acaceff9e26ca279df.png

ab01344d9a07e7f86dd5dbdfa84a3317.png

list在增加和修改的时候会对size、modCount值的变化

for循环底层是iterator,而for循环里面使用的remove是使用的list方法的remove方法,变化的是list方法中size、modCount,这样会导致迭代器中的size、modCount的值没有变化,就会抛出异常。

所以推荐在list的迭代器中做修改、删除的操作

ArrayList异常二:

ArrayList的subList引起的错误,

11e054711e92fd87be8f661c2305cd4f.png

查看源码发现: subList 返回的是 ArrayList 的内部类 SubList,并不是 ArrayList 而是 ArrayList的一个视图,对于 SubList 子列表的所有操作最终会反映到原列表上。

ArrayList异常三:

Arrays.asList()的异常

55f0ceb7b6794b3e4349e22dce3f678d.png

f75a023097fa00605d3c37c0a87533b0.png

asList 的返回对象是一个 Arrays 内部类,并没有实现集合的修改方法。Arrays.asList体现的是适配器模式,只是转换接口,后台的数据仍是数组

38831dcdd739c480b335080d4f8f39ad.png

3、面向对象

修饰符的访问权限

修饰符

类内部

同一个包

子类

任何地方

private

yes

default

yes

yes

yes

protected

yes

yes

yes

yes

public

yes

yes

yes

yes

成员变量与方法内部的变量重名时,希望在方法内部调用成员变量,怎么办呢?这时候只能使用this,例如:

public class Demo{

public String name;

public int age;

public Demo(){ this(“程建乐”,18) }

public Demo(String name, int age){

this.name = name;

this.age = age;

}

public void say(){ System.out.println("我叫" + name + ",已经" + age + "岁");}

}

1、在构造方法中调用另一个构造方法,调用动作必须置于最起始的位置。比如子类调用父类的方法,super必须放在最起始的位置

2、不能在构造方法以外的任何方法内调用构造方法。

3、在一个构造方法内只能调用一个构造方法。

关于一些在继承中的执行顺序

public class Person {

static{

System.out.println("我是父类静态代码块4");

}

{

System.out.println("我是父类代码块5");

}

public Person() {

System.out.println("我是父类的构造方法6");

}

}

public class Student extends Person {

static {

System.out.println("我是子类的静态代码块1");

}

{

System.out.println("我是子类的代码块2");

}

public Student() {

System.out.println("我是子类的构造方法3");

}

}

在new Student()的 时候,输出的顺序为: 4、1、5、6、2、3

深拷贝和浅拷贝

浅拷贝: 按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址 ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。即默认拷贝构造函数只是对对象进行浅拷贝复制(逐个成员依次拷贝),即只复制对象空间而不复制资源。

(1) 对于基本数据类型的成员对象,因为基础数据类型是值传递的,所以是直接将属性值赋值给新的对象。基础类型的拷贝,其中一个对象修改该值,不会影响另外一个。

(2) 对于引用类型,比如数组或者类对象,因为引用类型是引用传递,所以浅拷贝只是把内存地址赋值给了成员变量,它们指向了同一内存空间。改变其中一个,会对另外一个也产生影响。

深拷贝:

(1) 对于基本数据类型的成员对象,因为基础数据类型是值传递的,所以是直接将属性值赋值给新的对象。基础类型的拷贝,其中一个对象修改该值,不会影响另外一个(和浅拷贝一样)。

(2) 对于引用类型,比如数组或者类对象,深拷贝会新建一个对象空间,然后拷贝里面的内容,所以它们指向了不同的内存空间。改变其中一个,不会对另外一个也产生影响。

(3) 对于有多层对象的,每个对象都需要实现 Cloneable 并重写 clone() 方法,进而实现了对象的串行层层拷贝。

(4) 深拷贝相比于浅拷贝速度较慢并且花销较大。

aebb66a4d1154675e415867c5cb3a4d5.png

3bc20d4bb87de97c4dfefebf7283379d.png

4、枚举、注解

枚举定义:

枚举类型有一个公共的基本的父类,是java.lang.Enum类型,所以不能再继承别的类型

(1) 所有的枚举类型都隐含的继承自java.lang.Enum类,又因为Java是单继承的,所以一个枚举类 型不能继承任何其他的类。

(2) 枚举类型因为继承了java.lang.Enum类,又因为java.lang.Enum实现了java.lang.Comparable和java.io.Serializable接口,所以枚举类型也默认实现了这两个接口。

枚举类型的构造方法

(1)必须是private访问权限,不能使public权限。这样就可以保证在枚举类型定义的外部无法 使用new来创建枚举值。

​ (2)使用构造方法(当然是带参数的构造方法了!)赋值,则必须给所有的枚举值赋值;如果写了构造方法,却没有使用其用于赋值,则会报错。

枚举集合的使用:java.util.EnumSet和java.util.EnumMap是两个枚举集合。EnumSet保证集合中的元素不重复;EnumMap中的 key是enum类型,而value则可以是任意类型

枚举方法:

ordinal()方法: 返回枚举值在枚举类种的顺序。这个顺序根据枚举值声明的顺序而定。

compareTo()方法: Enum实现了java.lang.Comparable接口,因此可以比较象与指定对象的顺序。Enum中的compareTo返回的是两个枚举值的顺序之差。当然,前提是两个枚举值必须属于同一个枚举类,否则会抛出ClassCastException()异常

values()方法: 静态方法,返回一个包含全部枚举值的数组。

toString()方法: 返回枚举常量的名称。

valueOf()方法: 这个方法和toString方法是相对应的,返回带指定名称的指定枚举类型的枚举常量

equals()方法: 比较两个枚举类对象的引用

枚举集合的使用:java.util.EnumSet和java.util.EnumMap是两个枚举集合。EnumSet保证集合中的元素不重复;EnumMap中的 key是enum类型,而value则可以是任意类型

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值