Head First Java 和 AcWing Java课程做的总结5。
5.0 对象之母Object
在Java中的所有类都是从Object这个类继承出来的。
- Object类是所有类的源头,它是所有类的父类。
- 如果Java中没有共同的父类,那将无法让Java的开发人员创建出可以处理自定义类型的类,即无法写出像ArrayList这样可以处理各种类的类。
- 没有直接继承过其他类的类会是隐含的继承对象。
Object有什么?
-
//Obejct boolean equals(); Class getClass(); int hashCode(); String toString(); //只列出了类对象的一部分方法
使用Object类型的多态引用会付出代价
-
任何从
ArrayList<Object>
取出的东西都会被当作Object类型的引用而不管它原来是什么。 -
所有东西都以多态来当作Object会让对象看起来失去了真正的本质(但不是永久性的)。
-
Dog sameDog = getObject(aDog); //上面无法编译通过,虽然会返回同一个Dog,但是编译器会认为只能赋值给Object类型的变量 Object sameDog = getObject(aDog); //这样会过关,因为可以赋值任何东西给Object类型的引用
-
编译器是根据引用类型来判断有哪个method可以调用,而不是根据Object确实的类型。
-
就算你知道对象有这个功能,编译器还是会把它当作一般的Object来看待。编译器只管引用的类型,而不是对象的类型。
Object问答
-
Object这个类是抽象类吗?
- 不是,至少不是正式的Java抽象类,因为它可以被所有类继承下来的方法都实现程序代码,所以没有必须被覆盖过的代码。
-
那是否可以覆盖过Object的方法?
- 部分可以。但是有些被标记为final,这代表不能覆盖掉它们。强烈建议用自己写的类去覆盖掉hastCode()、equals()、toString()。
-
Object类是具体的,怎么会允许有人去创建Object的对象呢?这不就跟Animal对象一样不合理吗?
- 好问题,因为有时候就是需要一个通用的对象,一个轻量化的对象,它最常见的用途是在线程的同步化上面。
-
所以Object的主要目的是提供多态的参数与返回类型吗?
- Object类有两个主要的目的:作为多态让方法可以应付多种类型的机制,以及提供Java在执行前对任何对象都有需要的方法的实现程序代码(让所有的类都会集成到),有一部分方法是与线程有关。
-
既然多态类型这么有用,为什么不把所有的参数和返回类型都设定成Object类型呢?
- 类型检查安全,是Java保护程序代码的一项重要机制。在此机制下,不会让对象执行错误类型的动作。当某个对象是以Object类型来引用时,Java会把它当作Object类型的实例,这代表你只能调用由Object类中所声明的方法。
- Java是类型检查很强的程序语言,编译器会检查你调用的是否是该对象确实可以响应的方法。换句话说你只能从确实有该方法的类去调用。
5.1 类与对象
类定义一种全新的数据类型,包含一组变量和函数;对象是类这种类型对应的实例。
例如在一间教室中,可以将Student定义成类,表示“学生”这个抽象的概念。那么每个同学就是Student类的一个对象(实例)。
构造函数
-
构造函数看起来很像是方法,感觉上也很像方法,但它并不是方法,它带有new的时候会指向的程序代码,换句话说,这段程序代码会在你初始化一个对象的时候执行。
-
唯一能够调用构造函数的方法就是新建一个类。
-
就算程序员没写构造函数,编译器也会帮写一个。
-
//编译器帮写的 public Duck(){ }
-
和方法的不同是,方法有返回类型,构造函数没有返回类型。
-
一定要和类的名称相同。
-
-
构造函数会在对象能够被赋值给引用之前就执行。
- 构造函数让程序员有机会介入new的过程。
- 大部分人是使用构造函数来初始化对象的状态,也就是说给对象的实例变量赋值,但是一般还是使用 setter
- 如果某种对象在被初始化之前就使用,应该把初始化的程序代码放在构造函数中,然后把构造函数设定成需要参数的。
-
一定要有不需参数的构造函数,一个及以上构造函数,即重载构造函数。
- 自己写了有参构造函数,构造器则不会帮写无参构造函数。
- 重载,编译器只看参数的类型和顺序,而不是参数的名字。
构造函数及父类、继承:
- 构造函数应该是公有的吗?或者说私有的构造函数有什么作用?没有人能够调用它,所以也就没有人能创建该对象?
- 构造函数可以是公有、私有或不指定的。
- 私有的构造函数表示该类之外不能存取,并不是完全不能存取。
- 每个对象不只是保存自行声明的变量,还有从父类来的所有东西。
- 在创建新对象时,所有继承下来的构造函数都会执行。
源文件声明规则
- 一个源文件中只能有一个public类。
- 一个源文件可以有多个非public类。
- 源文件的名称应该和public类的类名保持一致。
- 每个源文件中,先写package语句,再写import语句,最后定义类。
类及其中元素的定义
public
: 所有对象均可以访问private
: 只有本类内部可以访问protected
:同一个包或者子类中可以访问- 不添加修饰符:在同一个包中可以访问
- 静态(带static修饰符)成员变量/函数与普通成员变量/函数的区别:
- 所有static成员变量/函数在类中只有一份,被所有类的对象共享;
- 所有普通成员变量/函数在类的每个对象中都有独立的一份;
- 静态函数中只能调用静态函数/变量;普通函数中既可以调用普通函数/变量,也可以调用静态函数/变量。
class Point {
private int x;
private int y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
public void setX(int x) {
this.x = x;
}
public void setY(int y) {
this.y = y;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public String toString() {
return String.format("(%d, %d)", x, y);
}
}
类的继承
每个类只能继承一个类。
class ColorPoint extends Point {
private String color;
public ColorPoint(int x, int y, String color) {
super(x, y);
this.color = color;
}
public void setColor(String color) {
this.color = color;
}
//函数覆盖
public String toString() {
return String.format("(%d, %d, %s)", super.getX(), super.getY(), this.color);
}
}
函数重载和函数覆盖的区别:
- 函数重载发生在同一个类的内部。这组函数具有相同的函数名,但是参数列表不相同,在函数调用过程中根据传入的实参类型,匹配最佳的函数并调用。
- 函数覆盖发生在子类与父类之间。父类中定义了一个函数,在子类中重新实现了这个函数,并且函数在子类和父类中具有相同的函数原型(函数名、参数列表),在调用函数过程中,根据对象的类型,调用相应类中的函数。
- 总结一下:函数重载是同一类中的不同方法,函数覆盖是不同类中的同一方法;重载函数的参数列表不同,覆盖函数的参数列表相同;重载函数调用时根据参数类型选方法,覆盖函数调用时根据对象类型选择方法。
- “名字隐藏”:指的是父类中有一组重载函数,子类在继承父类的时候如果覆盖了这组重载函数中的任意一个,则其余没有被覆盖的同名函数在子类是不可见的。
类的多态
public class Main {
public static void main(String[] args) {
Point point = new Point(3, 4);
Point colorPoint = new ColorPoint(1, 2, "red");
// 多态,同一个类的实例,调用相同的函数,运行结果不同
System.out.println(point.toString());
System.out.println(colorPoint.toString());
}
}
5.2 接口
interface
与class
类似。主要用来定义类中所需包含的函数。
Java的接口就好像是100%的纯抽象类。
接口也可以继承其他接口,一个类可以实现多个接口。
实现某接口必须实现它的所有的方法,因为这些方法都是public 和 abstract的。
接口的定义
接口中不添加修饰符时,默认为public。
interface Role{
public void greet();
//等价于public abstract void greet();
public void move();
public int getSpeed();
}
接口的继承
每个接口可以继承多个接口
interface Hero extends Role {
public void attack();
}
接口的实现
每个类可以实现多个接口
class Zeus implements Hero {
private final String name = "Zeus";
public void attack() {
System.out.println(name + ": Attack!");
}
public void greet() {
System.out.println(name + ": Hi!");
}
public void move() {
System.out.println(name + ": Move!");
}
public int getSpeed() {
return 10;
}
}
接口的多态
class Athena implements Hero {
private final String name = "Athena";
public void attack() {
System.out.println(name + ": Attack!!!");
}
public void greet() {
System.out.println(name + ": Hi!!!");
}
public void move() {
System.out.println(name + ": Move!!!");
}
public int getSpeed() {
return 10;
}
}
public class Main {
public static void main(String[] args) {
Hero[] heros = {new Zeus(), new Athena()};
for (Hero hero: heros) {
hero.greet();
}
}
}
接口做引用类型:
- 接口在做引用类型的时候,依靠的是实现接口的类;
- 接口引用指向实现接口类的对象;
- 也是多态的一种表现形式。