本文章仅用于记录学习Java时遇到的部分细节(01偏理论学习) - Felix
目录
> String、StringBuffer和StringBuilder
> + 号的使用
- 当数值两边都是数值型时,做加法运算
- 当左右两边有一方为字符串,则作拼接运算
System.out.printIn(100+98);//198
System.out.printIn("100"+98)://10098
System.out.printIn(100+3+"hello");//103hello
System.out.printIn("hello"+ 100 +3); //hello1003
>浮点数使用陷阱
看下面的案例:
可以看到num2得到的值是一个接近2.7的小数,而非2.7。无法得到相等结论。可知:
当我们对运算结果是小数进行相等判断时,要小心,应该是以两个数的差值的绝对值,在某个精度范围内判断。
if(Math.abs(num1-num2) < 0.00001){ System.out.println("差值非常小,到我的规定精度,认为相等"); }
> 字符编码表
- utf-8 (编码表,大小可变的编码)字母使用一个字节,汉字使用三个字节
- gbk(可以表示汉字,而且范围广)字母使用一个字节,汉字使用两个字节
- gb2312(可以表示汉字,gb2312 < gbk)
- big5 码(繁体中文,台湾,香港)
> 基本数据类型转换
- 自动提升原则:表达式结果的类型自动提升为 操作数中最大的类型
- byte、short、char 三者可以计算,在计算时首先转换成int类型
byte b = 16; //ok
short s = 14; //ok
short t = s + b; //错误,int->short
> String->char
- 字符串转成字符char -> 含义是指把字符串的第一个字符得到
String s = "123";
//s.charAt(0) 得到s字符串的第一个字符'1' 索引从0开始
System.out.println(s.charAt(0));
> swith语句
- 表达式数据类型,应与case后的常量类型一致,或者是可以自动转成可以相互比较的类型,比如输入的是字符,而常量是int
- swith(表达式) 中,表达式的返回值必须是:byte、short、int、char、enum、String
- case子句中的值必须是常量,而不能是变量
- default子句是可选的,当没有匹配的case时,执行default
- break语句用来在执行完一个case分支后使程序跳出switch语句块;如果没有写break,程序会顺序执行到switch结尾,除非遇到break
> 接受一个char
Scanner类没有nextChar()方法,因此需要读入char型时,可采用:
1.用next()代替,返回值为string;
2.写一个类继承Scannar,添加一个方法,这个方法需要用到next()方法,返回值为char型;
Scanner in = new Scanner(System.in);
char key = in.next().charAt(0);
> 区分对象及其引用
//p 是对象名(对象引用)
//new Person() 创建的对象空间(数据) 才是真正的对象
Person p = new Person();
> Java内存结构分析
- 栈:一般存放基本数据类型(局部变量)
- 堆:存放对象(Cat cat,数组等)
- 方法区:常量池(常量,比如字符串),类加载信息
> 方法重载
- 方法名:必须相同
- 形参列表:必须不同(形参类型、个数或顺序,至少有一样不同,参数名无要求)
- 返回类型:无要求
> 可变参数
访问修饰符 返回类型 方法名(数据类型... 形参名){
}
- 可变参数的实参可以为0个或任意多个
- 可变参数的实参可以为数组
- 可变参数的本质就是数组
- 可变参数可以和普通类型的参数一起放在形参列表 但必须保证可变参数在最后
- 一个形参列表中只能出现一个可变参数
class T{
public void f1(int...nums){
System.out.println("长度="+nums.length);
}
//细节:可变参数可以和普通类型的参数一起放在形参列表 但必须保证可变参数在最后
public void f1(String str,double...nums){
}
//细节:一个形参列表中只能出现一个可变参数
// public void f1(String...str,double...nums){
// }
}
> this访问构造器
访问构造器语法:this(参数列表);注意只能在构造器中使用(即只能在构造器中访问另一个构造器,必须放在第一条语句)
class D{
//细节:访问构造器语法:this(参数列表);注意只能在构造器中使用
// 即只能在构造器中访问另一个构造器
//注意:如果有构造器语法:this(参数列表);必须放置第一条语句
public D(){
this("jack",100);
System.out.println("D()构造器");
//这里去访问D(String name,int age)
}
public D(String name,int age){
System.out.println("D(String name,int age)构造器");
}
}
> 继承的构造器问题
- 当创建子类对象时,不管使用子类的哪个构造器,默认情况下总会去调用父类的无参构造器,super()。如果父类没有提供无参构造器,则必须在子类的构造器中用super去指定使用父类的哪个构造器完成对父类的初始化工作,否则,编译不会通过
- super在使用时,必须放在构造器第一行
- 结合上一个知识点可知,super()和this()这两个方法不能共存在一个构造器,因为他们都只能放在构造器第一行
- 父类构造器的调用不限于直接父类,将一直往上追溯直到Object类(顶级父类)
> this 与 super 的区别
> 继承细节
- 子类方法的返回类型和父类方法返回类型一样,或者是父类返回类型的子类
- ↑ eg: 父类返回类型是Objec,子类方法返回类型是String
- 子类方法不能缩小父类方法的访问权限
> 向上/向下转型
多态的向上转型
1)本质: 父类的引用指向了子类的对象
2)语法: 父类类型 引用名 =new 子类类型()
3)特点: 编译类型看左边,运行类型看右边。
可以调用父类中的所有成员(需遵守访问权限),不能调用子类中特有成员;
最终运行效果看子类的具体实现!
多态的向下转型
1) 语法:子类类型 引用名 = (子类类型) 父类引用;
2)只能强转父类的引用,不能强转父类的对象
3)要求父类的引用必须指向的是当前目标类型的对象
4)当向下转型后,就可以调用子类类型中所有的成员
> ==和equals方法
==是一个比较运算符
1.既可以判断基本类型,又可以判断引用类型
2.如果判断基本类型,判断的是值是否相等
3.如果判断引用类型,判断的是地址是否相等
equals是Object类中的方法
1.只能判断引用类型
2.默认判断地址是否相等,子类中往往重写该方法,用于判断内容是否相等。如Interge,String
> equals方法重写
应用实例:判断两个Person对象的内容是否相等,如果两个Person对象的各个属性值都一样,则返回true,反之false.
//重写Object的equals方法
public boolean equals(Object obj) {
//判断如果比较的两个对象是同一个对象,直接返回true
if(this == obj){
return true;
}
//类型判断
if(obj instanceof Person){ //是Person,我们才比较
//进行向下转型,因为需要得到obj的各个属性
Person p = (Person)obj;
return this.name.equals(p.name) && this.age == p.age
&& this.gender == p.gender;
}
//如果不是Person,则直接返回false
return false;
}
> toString方法
- 默认返回:全类名+@+哈希值的十六进制,子类往往重写toString方法,用于返回对象的属性信息
- 重写toString方法,打印或拼接对象时,都会自动调用该对象的toString形式
- 当直接输出一个对象时,toString方法都会被默认的调用。比如System.out.println(monster),就会默认调用monster.toString()
> 类变量
- 类变量是在类加载时就初始化了,也就是说,即使你没有创建对象,只要类加载了,就可以使用类变量了
- 类变量的生命周期是随类的加载开始,随着类消亡而销毁
> 用到final的情况
- 当不希望类被继承时
- 当不希望父类的某个方法被子类覆盖/重写时
- 当不希望类的某个属性的值被修改时
- 当不希望某个局部变量被修改时
> 自定义枚举
- 不需提供setXXX方法,因为枚举对象值通常为只读
- 对枚举对象/属性使用final+static共同修饰,实现底层优化
- 枚举对象名通常使用全部大写,常量的命名规范
> enum关键字实现枚举
- 当我们使用enum 关键字开发一个枚举类时,默认会继承Enum类,而且是一个final类
- 传统的 public static final Season2 SPRING = new Season2(春天”"温暖");简化成 SPRING(“春天”,"温暖”),这里必须知道,它调用的是哪个构造器
- 如果使用无参构造器 创建 枚举对象,则实参列表和小括号都可以省略
- 当有多个枚举对象时,使用,间隔,最后有一个分号结尾
- 枚举对象必须放在枚举类的行首
>三元运算符
public class eg1 {
public static void main(String[] args) {
Object object = true? new Integer(1):new Double(2.0);
System.out.println(object);
}
}
输出结果为1.0而非1。
因为三元运算符是一个整体,整体优先级先提升到了Double,再进行的运算。
> final
当使用final修饰基本类型变量时,不能对基本类型变量重新赋值,因此基本类型变量不能被改变。但对于引用类型变量而言,它保存的仅仅是一个引用,final只保证这个引用变量所引用的地址不会改变,即一直引用同一个对象,但这个对象完全可以发生改变。例如某个指向数组的final引用,它必须从此至终指向初始化时指向的数组,但是这个数组的内容完全可以改变。
> 看起来String对象可变的幻觉
String中提供了一些看似可以改变String对象的方法,但实际上它们已经是指向了一个新建的对象。
public static void main(String[] args) {
String str1 = "hello";
// 打印str1的内存地址
System.out.println("str1的内存地址:" + System.identityHashCode(str1));
String str2 = "world";
str1 += str2;
// str1的内存地址已经改变了
System.out.println("执行+=后str1的内存地址:" + System.identityHashCode(str1));
System.out.println("拼接之后str1的值:" + str1);
String str3 = "123";
// 创建一个新的对象来保存拼接之后的值
String str4 = str3.concat("456");
// concat操作不会改变原来str3的值
System.out.println("str3的值:" + str3);
System.out.println("str4的值:" + str4);
}
//运行结果
str1的内存地址:1922154895
执行+=后str1的内存地址:883049899
拼接之后str1的值:helloworld
str3的值:123
str4的值:123456
str1+=str2实际上是执行了str1=(new StringBuilder()).append(str2).toString();前后实际额外产生了一个StringBuilder与一个helloworld的字符串常量。
> String、StringBuffer和StringBuilder
- String:不可变字符序列,效率低,但复用率高
- StringBuffer:可变字符序列,效率较高(增删),线程安全
- StringBuilder:可变字符序列,效率最高,但线程不安全
- 如果字符串存在大量的修改操作,一般使用 StringBuffer 或StringBuilder
- 如果字符串存在大量的修改操作,并在单线程的情况,使用 StringBuilder
- 如果字符串存在大量的修改操作,并在多线程的情况,使用 StringBuffer
- 如果我们字符串很少修改,被多个对象引用,使用String,比如配置信息等
>String和StringBuffer的转换
//String->StringBuffer
String str = "hello tom";
//方式1 使用构造器
//注意:返回的才是StringBuffer对象,对str本身无影响
StringBuffer stringBuffer = new StringBuffer(str);
//方式2 使用的是append方法
StringBuffer stringBuffer1 = new StringBuffer();
stringBuffer1 = stringBuffer1.append(str);
//StringBuffer->String
StringBuffer stringBuffer2 = new StringBuffer("仪宝仪宝");
//方式1 使用StringBuffer提供的to String方法
String s = stringBuffer2.toString();
//方式2 使用构造器
String s1 = new String(stringBuffer2);
> 自定义排序
Array中的sort方法默认提供从小到大的排序,若需要从大到小和其他自定义排序,可通过实现Comparator接口匿名内部类定制排序。
//1.price从小到大
Arrays.sort(books, new Comparator() {
@Override
public int compare(Object o1, Object o2) {
Book book1 = (Book)o1;
Book book2 = (Book)o2;
//因为继承的是int,不能修正为double(否则不是重写了)
//所以利用下面进行转换
double priceVal = book1.price-book2.price;
if(priceVal>0)
return 1;
else if(priceVal<0)
return -1;
else
return 0;
}
}
//2.自定义排序(此处以书的长度排序为例子)
Arrays.sort(books, new Comparator() {
@Override
public int compare(Object o1,Object o2) {
Book book1 = (Book1)o1; //向下转型
Book book2 = (Book1)o2;
//1-2 -> 从小到大 2-1 -> 从大到小
return book1.getName().length()-book2.getName().length();
}
}
> BigInteger 保存较大的整型
BigInteger bigInteger = new BigInteger("2378888888889999999999999");
System.out.println(bigInteger);
> BigDecimal 保存精度较高的浮点型(小数)
BigDecimal bigDecimal = new BigDecimal(1999.111111111111111111999999);
System.out.println(bigDecimal);
常见方法:add加 subtract减 multiply乘 divide除
// 除 可能抛出异常ArithmeticException(因为精度很高,但又除不尽)
//解决方法:在调用divide方法时,指定精度即可
//BigDecimal.ROUND_CEILING 若有无限循环小数,就会保留分子的精度
System.out.println(bigDecimal.divide(bigDecimal2,BigDecimal.ROUND_CEILING));
> Iterator(迭代器)的使用
所有实现了Collection接口的集合类都有一个iterator()方法,用以返回一个实现了Iterator接口的对象,即可以返回一个迭代器。Iterator仅用于遍历集合,本身并不存放对象
Collection col = new ArrayList();
col.add(new Book("三国演义","罗贯中",10.1));
col.add(new Book("小李飞刀","古龙",5.1));
//遍历col集合
//1.先得到col对应的迭代器
Iterator iterator = col.iterator();
//2.使用while循环遍历
while(iterator.hasNext()){//判断是否还有数据
// 返回下一个元素,类型是Object
Object obj =iterator.next();
System.out.println("obj="+obj);
}
//3.如果希望再次遍历,需要重置我们的迭代器
iterator =col.iterator(); //重置
System.out.println("===第二次遍历===");
while(iterator.hasNext()){
Object obj =iterator.next();
System.out.println("obj="+obj);
}
>List的三种遍历方式
1)方式一:使用iterator
Iterator iter = list.iterator();
while(iter.hasNext(){
Object o = iter.next();
System.out.println(o);
}
2)方式二:使用增强for
for(Object o:list){
System.out.println(o);
}
3)方式三:使用普通for
for(int i=0;i<list.size();i++){
System.out.println(list.get(i));
}
> List接口的实现类比较
List接口的实现类 | 底层结构 | 增删效率 | 改查的效率 | 线程安全 |
ArrayList | 可变数组 | 较低,数组扩容(1.5倍) | 较高 | 不安全 |
LinkedList | 双向链表 | 较高,通过链表追加 | 较低 | 不安全 |
Vector | 可变数组 | 较低,数组扩容(2倍) | 较低 | 安全 |
- 改查操作多 -> ArrayList/vector
- 增删操作多 -> LinkedList