Java基础
基础不牢,地动山摇。要成为一名优秀的Java开发工程师,必须对Java基础掌握的非常牢固。学习在于日积月累,坚持不懈,以至熟能生巧尔。
基本类型的特点是它们是直接存储在内存中的,占用固定的内存空间,并且没有对应的方法或属性。
包装类型是为了方便在基本类型和对象之间进行转换而引入的。每个基本类型都有对应的包装类型,包装类型的命名与对应的基本类型相似,但首字母大写。例如:
Byte:对应 byte
Short:对应 short
Integer:对应 int
Long:对应 long
Float:对应 float
Double:对应 double
Character:对应 char
Boolean:对应 boolean
包装类型是类,因此它们具有对应的方法和属性,可以进行更多的操作,比如进行数学计算、转换为字符串等。包装类型还提供了常量和静态方法来操作基本类型的数据。
另外,包装类型还具有自动装箱(Autoboxing)和自动拆箱(Unboxing)的功能。自动装箱是指将基本类型自动转换为对应的包装类型,而自动拆箱是指将包装类型自动转换为对应的基本类型。
总结起来,基本类型是简单的数据类型,直接存储在内存中,而包装类型是对基本类型的封装,提供了更多的功能和操作。在需要使用对象的场景下,可以使用包装类型,而在需要更高效的存储和计算时,可以使用基本类型。
为了提高性能和节省内存,Java在某些情况下会对包装类型的对象进行缓存。具体来说,对于某些范围内的整数和一些常用的浮点数,Java会缓存对应的包装类型对象,以便重复使用,而不是每次都创建新的对象。
缓存机制适用于以下的情况:
整数缓存:对于byte和short范围内的整数(-128到127之间),以及Integer类中预先缓存的整数(-128到127之间),会被缓存为对象,以便重复使用。
Integer a = 10;
Integer b = 10;
System.out.println(a == b); // 输出 true
常用浮点数缓存:对于Float和Double类中预先缓存的一些常用浮点数(如0.0、1.0等),会被缓存为对象,以便重复使用。
Float a = 1.0f;
Float b = 1.0f;
System.out.println(a == b); // 输出 true
需要注意的是,缓存机制只适用于自动装箱的情况,即通过赋值操作或方法调用将基本类型转换为包装类型。如果使用显式的构造函数创建包装类型对象,那么不会使用缓存,而是每次都创建新的对象。
Integer a = new Integer(10);
Integer b = new Integer(10);
System.out.println(a == b); // 输出 false
因此,在比较包装类型对象时,应该使用.equals()方法而不是==运算符,以确保比较的是对象的值而不是引用。
Integer a = 10;
Integer b = 10;
System.out.println(a.equals(b)); // 输出 true
需要注意的是,缓存机制是Java虚拟机的实现相关的,不同的虚拟机实现可能会有不同的缓存策略。因此,在编写代码时,不应该依赖于包装类型的缓存机制,而应该始终使用.equals()方法来比较包装类型对象的值。
构造方法(Constructor)是用于创建对象并初始化对象的特殊方法。构造方法具有以下几个特点:
方法名与类名相同:构造方法的方法名必须与类名完全相同,包括大小写。
没有返回类型:构造方法没有返回类型,包括 void
。它们的主要目的是初始化对象,而不是返回值。
可以有参数:构造方法可以接受参数,用于在创建对象时传递初始值。
可以重载:同一个类可以定义多个构造方法,只要它们的参数列表不同即可。这样可以根据不同的参数组合来创建对象。
默认构造方法:如果在类中没有显式定义任何构造方法,Java会自动提供一个无参的默认构造方法。默认构造方法不接受任何参数,执行的操作通常是对成员变量进行默认初始化。
构造方法在对象创建时被调用,用于初始化对象的状态。它们通常用于执行以下操作:
初始化成员变量:构造方法可以在对象创建时对成员变量进行初始化,为对象提供初始值。
执行必要的操作:构造方法可以执行一些必要的操作,如打开文件、建立数据库连接等。
构造方法可以被继承,但不能被直接重写(override)。子类可以定义与父类相同的构造方法,但并不是重写父类的构造方法,而是在子类中提供了一个独立的构造方法。在子类的构造方法中,可以通过使用 super()
关键字调用父类的构造方法来完成父类的初始化。
需要注意的是,如果父类中没有无参的构造方法,而子类又没有显式地调用父类的构造方法,那么编译器会报错。在这种情况下,子类需要显式地调用父类的构造方法,通过使用 super()
关键字来指定调用父类的特定构造方法。
总结起来,构造方法是用于创建对象并初始化对象的特殊方法,具有与类名相同、没有返回类型、可以有参数、可以重载的特点。它们在对象创建时被调用,用于执行对象的初始化操作。构造方法可以被继承,但不能被直接重写。
当父类中没有无参的构造方法,而子类又没有显式地调用父类的构造方法时,编译器会报错。这是因为在创建子类对象时,会隐式地调用父类的无参构造方法来完成父类的初始化操作。如果父类中没有无参构造方法可供调用,编译器无法确定如何正确地初始化父类,因此会产生编译错误。
是的,当在父类中定义了有参数的构造方法后,如果没有显式地定义无参构造方法,那么父类就不再具有默认的无参构造方法。下面是一个示例来说明这种情况:
class Parent {
public Parent(int value) {
// 父类构造方法接受一个int类型的参数
// 其他代码...
}
}
class Child extends Parent {
public Child() {
// 子类的构造方法没有显式地调用父类的构造方法
// 其他代码...
}
}
在上面的示例中,父类 Parent
中定义了一个带有一个 int
类型参数的构造方法,但没有无参构造方法。子类 Child
的构造方法没有显式地调用父类的构造方法。
当我们尝试创建 Child
类的对象时,编译器会报错,因为编译器无法找到合适的父类构造方法来初始化父类。此时,我们需要在子类的构造方法中显式地调用父类的构造方法,通过使用 super()
关键字来指定调用父类的特定构造方法。
修正后的示例代码如下:
class Parent {
public Parent(int value) {
// 父类构造方法接受一个int类型的参数
// 其他代码...
}
}
class Child extends Parent {
public Child() {
super(10); // 显式地调用父类的构造方法
// 其他代码...
}
}
在修正后的代码中,子类 Child
的构造方法使用 super(10)
调用了父类 Parent
的构造方法,传递了一个 int
类型的参数。这样就能够正确地初始化父类,并且编译器不会报错。
封装(Encapsulation):封装是将数据和操作数据的方法封装在一起,形成一个独立的、可复用的类。通过封装,我们可以隐藏类的内部实现细节,只暴露必要的接口给外部使用。这样可以提高代码的安全性和可维护性,同时也方便了代码的复用和扩展。
继承(Inheritance):继承是通过定义一个新的类来继承已有类的属性和方法。继承可以建立类之间的层次关系,使得子类可以继承父类的特性,并且可以在此基础上进行扩展或修改。通过继承,我们可以实现代码的重用,减少重复编写类似的代码,同时也可以实现多态。
多态(Polymorphism):多态是指同一类型的对象在不同的情况下表现出不同的行为。多态可以通过继承和接口实现。在多态中,父类的引用可以指向子类的对象,通过父类的引用调用方法时,根据具体的对象类型,会执行对应子类的方法。多态提高了代码的灵活性和可扩展性,使得程序更易于扩展和维护。
这三大特征共同构成了面向对象编程的基础,通过封装、继承和多态,我们可以更好地组织和管理代码,提高代码的可读性、可维护性和可扩展性。
共同点:
区别:
总的来说,接口更加抽象和规范,用于定义类的行为和能力,强调多态和约束;抽象类更加具体,用于作为子类的基类,提供共享的属性和方法,并可以包含一些默认的实现。选择使用接口还是抽象类,取决于具体的需求和设计目标。
浅拷⻉:浅拷⻉会在堆上创建⼀个新的对象(区别于引⽤拷⻉的⼀点),不过,如果原对象内部的属性是引⽤类型的话,浅拷⻉会直接复制内部对象的引⽤地址,也就是说拷⻉对象和原对象共⽤同⼀个内部对象。
深拷⻉ :深拷⻉会完全复制整个对象,包括这个对象所包含的内部对象。
那什么是引⽤拷⻉呢? 简单来说,引⽤拷⻉就是两个不同的引⽤指向同⼀个对象。
对于基本数据类型来说,“ == ”⽐的是值。
对于引⽤数据类型来说,” == “⽐的是对象的内存地址。
equals() 不能⽤于判断基本数据类型的变量,只能⽤来判断两个对象是否相等。 equals() ⽅法存在于 Object 类中,⽽ Object 类是所有类的直接或间接⽗类,因此所有的类都有 equals() ⽅法。
“ = = ” 运算符用于比较两个对象的引用是否指向同一个内存地址。它比较的是对象的引用值,即比较两个对象是否是同一个对象。当使用“ == ”比较两个对象时,它会检查它们的引用是否相等,如果引用指向同一个内存地址,则返回true,否则返回false。
equals()方法是Object类中定义的方法,用于比较两个对象的内容是否相等。默认情况下,equals()方法与” == “”运算符的行为相同,即比较两个对象的引用是否相等。但是,可以通过在类中重写equals()方法来改变其行为,使其比较对象的内容而不是引用。
重写equals()方法时,通常需要满足以下几个条件:
总结:
“” == “比较的是对象的引用是否相等。
equals()比较的是对象的内容是否相等,可以根据需要重写该方法来改变比较的行为。
在使用equals()方法进行对象比较时,需要注意满足重写equals()方法的条件。
hashCode()
是Java中的一个方法,定义在Object
类中,用于返回对象的哈希码(hash code)。哈希码是一个整数值,用于快速确定对象在哈希表等数据结构中的存储位置。
hashCode()
方法的主要作用如下:
在哈希表中的使用:哈希表是一种常用的数据结构,如HashMap
、HashSet
等,它们使用哈希码来确定对象在表中的存储位置。通过计算对象的哈希码,可以快速定位对象在哈希表中的存储位置,提高查找和插入的效率。
作为对象的标识符:哈希码在某些情况下可以作为对象的唯一标识符。如果两个对象的哈希码相同,不一定表示它们相等,但如果两个对象不相等,则它们的哈希码一定不同。因此,哈希码可以用于快速判断对象是否相等,从而提高对象比较的效率。
在集合中的使用:在使用集合类(如HashSet
、HashMap
等)存储对象时,为了保证集合中的元素不重复,会使用hashCode()
方法来判断两个对象是否相等。当向集合中插入元素或者从集合中查找元素时,会先比较对象的哈希码,如果哈希码相等再调用equals()
方法进行进一步的比较。
需要注意的是,为了保证对象的一致性,当重写equals()
方法时,通常也需要同时重写hashCode()
方法,以满足以下规则:
equals()
方法的定义),那么它们的哈希码必须相等。equals()
方法的定义)。总结:
hashCode()
方法用于返回对象的哈希码,主要用于在哈希表中确定对象的存储位置和作为对象的标识符。在使用集合类存储对象时,通常需要同时重写equals()
和hashCode()
方法,以确保对象的一致性和正确性。
在Java中,final
是一个关键字,用于修饰类、方法和变量。
修饰类:当一个类被声明为final
时,它表示该类是最终的,不能被继承。这意味着其他类无法扩展或继承这个final
类,从而保护类的完整性和安全性。例如,String
类就是一个final
类,不能被继承。
修饰方法:当一个方法被声明为final
时,它表示该方法是最终的,不能被子类重写。这样做的目的是为了避免子类修改父类的行为,保持方法的一致性和稳定性。通常,final
方法是被认为是不可变的或者具有特殊用途的方法。
修饰变量:当一个变量被声明为final
时,它表示该变量是常量,只能被赋值一次,并且在后续的操作中不能修改其值。一旦被赋值,final
变量的值将保持不变。常量通常使用大写字母表示,并且在声明时就需要进行初始化。final
变量在多线程环境下具有线程安全性。
final
关键字的作用如下:
final
关键字可以保护类、方法和变量的完整性,防止被修改或继承,从而提供额外的安全性。final
方法和变量,编译器可以进行优化,例如内联方法调用和常量折叠,以提高程序的执行效率。final
关键字可以明确表示类、方法或变量的意图,使代码更易读、理解和维护。需要注意的是,final
关键字并不是必须使用的,它根据需要和设计要求来决定是否使用。在设计类、方法和变量时,考虑使用final
关键字可以增加代码的可靠性和可维护性。
Case1:小数点后第一位 = 5
正数:Math.round(11.5) = 12
负数:Math.round(-11.5) = -11
Case2:小数点后第一位 < 5
正数:Math.round(11.49) = 11
负数:Math.round(-11.49) = -11
Case3:小数点后第一位 > 5
正数:Math.round(11.69) = 12
负数:Math.round(-11.69) = -12
总结:
Math类中提供了三个与取整有关的方法:ceil,floor,round 这些方法的作用于它们的英文名称的含义相对应
例如:ceil的英文意义是天花板,该方法就表示向上取整,Math.ceil(11.3)的结果为12,Math.ceil(-11.6)的结果为-11;
floor的英文是地板,该方法就表示向下取整,Math.floor(11.6)的结果是11,Math.floor(-11.4)的结果-12;
最难掌握的是round方法:他表示“四舍五入”,算法为Math.floor(x+0.5),即将原来的数字加上0.5后再向下取整,
所以,Math.round(11.5)的结果是12,Math.round(-11.5)的结果为-11.
Math.round( )符合这样的规律:小数点后大于5,正数和负数的个位数全部加1,
小数点后等于5,正数的个位数加1,
小数点后小于5,正数和负数的个位数全不加1(保持个位数原数)。
注:这里所说的加1,是先将负号去掉,加完后,再变为负号。