其实转语言来说,语法都比较简单,花个三天就会了,但最主要的是熟悉各种API,比如用惯了C++的STL,再来学新的API,就会觉得很不习惯,就拿vector和ArrayList来说,vector就可以直接[]
访问,但是Java没有提供操作符重载,就只能用函数来获取了,怪不习惯的。这就好比你之前都是在学一门语言的语法,但是更费时的是去背单词,而往往一门语言学的好不好就是看会的单词多不多。这个比喻不知道恰不恰当,哈哈哈~
接下来Java的学习,就是去熟悉各种常用的API了,我目前能想到的主要有各种数据结构、网络IO、多线程同步互斥、文件等等,把这些都写一遍,才算是初步熟悉Java了吧,然后再去学各种框架。在学习的时候,我也会时常去和C++的一些API做对比的,方便自己和读者们理解。
Object类是Java中所有类的祖宗类,因此,Java中所有类的对象都可以直接使用Object类中提供的一些方法。
toString
和equals
是可以在类中进行重写的,而clone
会调用父类Object中的clone方法,并且需要在类中表明Cloneable
才行。
package api_object;
import java.util.Objects;
// Cloneable是一个标记接口
// 规则
public class Student implements Cloneable{
private String name;
private int age;
private int[] scores;
public Student() {}
public Student(String name, int age) {
this.name = name;
this.age = age;
this.scores = new int[2];
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return age == student.age && Objects.equals(name, student.name);
}
@Override
protected Object clone() throws CloneNotSupportedException {
// super去调用父类Object中的clone方法
Student s2 = (Student) super.clone();
s2.scores = s2.scores.clone();
return s2;
}
public int[] getScores() {
return scores;
}
public void setScores(int[] scores) {
this.scores = scores;
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
深克隆和浅克隆
我们知道java的数据类型分为引用类型和值类型,因此就有了浅克隆和深克隆一说。
对于浅克隆而言,一方数据改变后,另一方数据也会跟着变化
对于深克隆而言,原型对象和克隆对象之间没有任何关联,指向不同的地址。
所以为了实现深克隆,就需要对对象进行递归克隆,clone中嵌套clone:首先让源对象调用克隆方法获得克隆的对象,然后获得被克隆对象的引用类型的成员变量对象,对该成员变量对象调用克隆方法,此时成员变量对象也被克隆了一份,最后将该克隆的成员变量对象,设置为克隆对象的新的成员变量,再返回该被克隆的对象,即可实现深克隆。
该类中都是些static方法,且是final类,用于操作对象或操作前检查某些条件。
对于equals来说,为什么不用对象自己的equals方法,就是为了防止空指针报错,所以官方更推荐Objects的equals方法。
Java万物皆对象,所以对于基本类型来说,也包装成了对应的类。(String已经包装过了)
对于包装类来说,主要是为了模板和各种API的使用。
拿Integer来说
Integer i = Integer.valueOf(7);
Integer j = 7; // 自动装箱
int p = i; // 自动拆箱
当然还有一些包装类的常见操作,比如:
to_string()
)public static String toString(double d)
stoi()
)public static int parseInt(String s)
public static Integer valueOf(String s)
(最推荐这个)当然,还可以这样操作:Integer a = 23; String s = a + ""
相当于一个容器,这个类在String的基础上,提供了更多的一些方法做修改,效率会更高,代码会更简洁。
StringBuilder就相当于是C++的string了,是一个动态可变字符串,而Java原本的String是不可变的,修改效率非常地下
而StringBuilder和StringBuffer的用法一模一样,但是StringBuilder是线程不安全的,StringBuffer是线程安全的。
这个类是JDK8才有的,和StringBuilder一样,也是个容器。
好处:不仅能提高字符串的操作效率,并在一些场景下使用它操作字符串,代码会更简洁。
C++能有这玩意儿?这个是真的牛啊!!!
关于这个自定义排序,和C++有点不同。Java官方的约定是:
根据这样,得到的是升序,那么在代码的体现最直接了当的就是o1 > o2
即可。然而C++确是o1 < o2
,注意区分。
作用:用于简化匿名内部类的代码写法
格式:
(被重写方法的形参列表)->{
被重写方法的方法体代码。
}
对比下C++的匿名函数
[](形参列表) {
代码。
}
@FunctionalInterface
的注解,有该注解的接口必定是函数式接口省略写法
int[] nums = new int[]{123, 321, 153};
Arrays.setAll(nums, value -> nums[value]*2);
for (int num : nums) {
System.out.println(num);
}
静态方法引用
类名::静态方法
如果某个Lambda表达式只是调用一个静态方法,并且前后参数的形式一致,就可以使用静态方法引用。
实例方法引用
对象名::实例方法
如果某个Lambda表达式只是调用一个实例方法,并且前后参数的形式一致,就可以使用实例方法引用。
特定类型方法的引用
类型::方法
如果某个Lambda表达式里只是调用一个实例方法,并且前面参数列表中的第一个参数是作为方法的主调,后面的所有参数都是作为该实例方法的入参的,则此时可以使用特定类型的方法引用。
构造器引用
类名::new
如果某个Lambda表达式只是在创建对象,并且前后参数情况一致,就可以使用构造器引用。