面向对象基础
java 面向对象基础
方法
- 一个class可以有多个field
class Person {
public String name;
public int age;
}
如果直接用public直接暴露给外面的代码,会造成field很容易被修改,造成代码混乱。所以用private进行修饰
class Person {
private String name;
private int age;
}
这样外面的代码就无法访问了,在对象构造的过程中,直接传入参数,或者通过在类中使用public方法进行修改field
public class Main{
public static void main(String[] args){
Person ming = new Person();
ming.setAge(20);
ming.setName("xiao ming");
System.out.println(ming.getName() + "," + ming.getAge());
}
}
class Person{
private String name;
private int age;
public String getName(){
return this.name;
}
public void setName(String name){
this.name = name;
}
public int getAge(){
return this.age;
}
public void setAge(int age){
if(age < 0 || age > 100){
throw new IllegalArgumentException("invalid age value");
}
this.age = age;
}
}
-
private 方法保证只有内部可以访问,外部无法访问
-
this 变量始终指向当前实例
-
方法可以包含任意参数,但是传参的时候必须根据参数定义一一传递
public class Test{
public static void main(String[] args){
public void setNameAndAge(String name, int age){
// ...
}
}
}
- 可变参数
class Group{
private String[] names;
public void setName(String... names){
this.names = names;
}
}
class Main{
public void main(String[] args){
Group g = new Group();
g.setNames("Xiao Ming", "Xiao Hong", "Xiao Jun"); // 传入3个String
g.setNames("Xiao Ming", "Xiao Hong"); // 传入2个String
g.setNames("Xiao Ming"); // 传入1个String
g.setNames(); // 传入0个String
}
}
-
参数绑定
修改外部的局部变量
n
,不影响实例p
的age
字段,原因是setAge()
方法获得的参数,复制了n
的值,因此,p.age
和局部变量n
互不影响。基本类型参数的传递,是调用方值的复制
引用类型参数的传递,调用方的变量,和接收方的参数变量,指向的是同一个对象。双方任意一方对这个对象的修改,都会影响对方
String类型虽然是引用类型 但是是不可变的 所以当外部的String类型修改时是指向改变了 但是方法内部的指向不会改变
构造方法
-
构造方法可以保证在实例化的时候进行传参
-
去除set方法,并且在类里面增加一个方法
public 类名(String name, int age){
this.name = name;
int age = age;
}
- 默认构造方法
默认情况下会创建,但是当我们创建了构造方法的其工况下就不会有了,构造方法的参数不同就是不同的构造方法,所以可以创建多个构造方法
public 类名(){
}
- 类里面在定义时可以预先对参数进行初始化操作
class Person{
private String name = "Unnamed";
private int age = 10;
// ...
}
-
多个构造方法
可以定义多个构造方法,编译器通过构造方法的参数数量,类型,位置进行自动调用
class Person{
private String name;
private int age;
public Person(String name, int age){
this.name = name;
this.age = age;
}
public Person(String name){
this.name = name;
}
}
- 一个构造方法还可以调用其他构造方法,通过 this(...) 即可
class Person{
private String name;
private int age;
public Person(String name, int age){
this.name = name;
this.age = age;
}
public Person(String name){
this(name, 18); // 此处调用上面的构造方法
}
}
方法重载
- 在一个类中,如果有多个方法重名(那么构造方法属不属于方法的重载?),只有参数的不同,那么这一组方法称为同名方法,这种情况称为方法的重载
class Hello{
public void hello(){
System.out.println("Hello");
}
public void hello(String name){
System.out.println("Hello" + this.name);
}
// ...
}
- 方法重载指的是多个方法的方法名相同,但是参数不同。重载应该完成相似功能。重载的返回值类型应该相同
继承
- 定义一个Person类
class Person{
private String name;
private int age;
public void setName(String name){}
public String getName(){}
public void setAge(int age){}
public int getAge(){}
}
- 定义一个Student类
class Student{
private String name;
private int age;
private int score;
public void setName(String name){}
public String getName(){}
public void setAge(int age){}
public int getAge(){}
public void setScore(int score){}
public int getScore(){}
}
- 可知Student可以有Person类扩展而来,使用 extends 关键字进行继承
class Student extends Person{
private int score;
public void setScore(int score){}
public int getScore(){}
}
子类自动获得了父类的所有字段,严禁定义与父类重名的字段
术语中
Person 称为 超类(super class),父类(parent class),基类(base class)
Student 称为 子类(subclass),扩展类(extended class)
-
Java只允许一个class继承自一个类,因此,一个类有且仅有一个父类。
-
protected 关键字
继承有个特点,就是子类无法访问父类的private字段或者private方法
为了让子类可以访问父类的字段,我们需要把private改为protected。用protected修饰的字段可以被子类访问protected
关键字可以把字段和方法的访问权限控制在继承树内部,一个protected
字段和方法可以被其子类,以及子类的子类所访问 -
super 关键字
子类引用父类的字段时,可以用
super.fieldName
如果父类没有默认的构造方法,子类就必须显式调用
super()
并给出参数以便让编译器定位到父类的一个合适的构造方法。这里还顺带引出了另一个问题:即子类不会继承任何父类的构造方法。子类默认的构造方法是编译器自动生成的,不是继承的
-
final 关键字阻止继承(看多态最后面final总结)
正常情况下,只要某个class没有final修饰符,那么任何类都可以从该class继承。
从Java 15开始,允许使用sealed修饰class,并通过permits明确写出能够从该class继承的子类名称。
public sealed class Shape permits Rect, Circle, Triangle {
...
}
-
向上转型
一般指的是 父类型 的 变量 指向了 子类型 的实例
-
向下转型
一般指的是 子类型 的 变量 指向了 父类型 的实例,这种做法很有可能会导致失败,因为子类型可能有一些自己单独的变量或者是方法,转型会报错,为了避免报错,在转型前需要使用 instanceof 操作符判断
s instanceof Person;
-
区分继承和组合
继承是is关系
组合是has关系
组合的使用方式是在子类字段里面新建一个对象
private Book book;
在子类构造方法里面进行实例化并传参(如果需要的话),在外面进行实例的方法调用
public Student(String name, int age, String bookName){ super(name, age); book = new Book(bookName); // 实例化 } public String getBookName(){ return book.getName(); }
多态
-
在继承关系中,如果子类定义了一个和父类签名完全相同的方法,称为覆写(override)
如果方法的签名不相同,那么就是Overload
-
@Override 可以让编译器帮忙检查覆写的正确性,但是不是必要的
@Override
public void run(){}
-
Java的实例方法调用是基于运行时的实际类型的动态调用,而非变量的声明类型
-
多态是指,针对某个类型的方法调用,其真正执行的方法取决于运行时期实际类型的方法。
-
覆写Object方法
-
调用super
如果在子类中需要调用父类被覆写的方法,可以用super关键字
class Person{
protected String name;
public void hello(){
System.out.println("Hello " + this.name);
}
}
class Student{
@Override
public void hello(){
System.out.println(super.hello + " !");
}
}
- final
1. 用于类内的方法,可以保证此方法不会被覆写
2. 用于类自身,可以保证此类不会被继承
3. 用于类的实例字段,保证字段在初始化或者实例化之后不会被修改
class Person{
public final void run(){
// ...
}
}
class final Person{
// ...
}
class final Person{
protected final String name;
public Person(String name){
this.name = name;
}
}
抽象类
-
abstract 字段
父类的方法不实现任何功能,仅仅是为了定义一个方法签名,目的是为了让子类去覆写他,那么此方法可以定义为一个抽象方法,抽象方法没有
{}
,父类本身也必须是abstractabstract class Person{ public abstract void run(); }
-
抽象类无法实例化,只能被继承
Pass 这里疑问! 如果一个抽象类定义了两个抽象方法,那么子类必须两个抽象方法都要覆写吗?
实现之后可以看到,必须每个抽象方法都要被覆写
abstract class Person{ abstract public void run(); } class Student extends Person{ public void test(){ System.out.println("test"); } }
-
面向抽象编程
// 父类定义规范 abstract class Person{ abstract public void run(); } // 子类进行继承并覆写方法 class Student extends Person{ @Override public void run(){ System.out.println("Student"); } } // 利用向上转型的做法,可以不需要子类就可以实现业务逻辑 Person s = new Student();
接口
-
接口定义的是规范, 保证所有子类有相同的接口
-
所过某个抽象类没有字段,所有的方法都是抽象方法,那么这个抽象类可以改为接口。
abstract class Person{ abstract public void run(); abstract public String getName(); } // 上面的可以更改为 interface Person{ void run(); String getName(); }
接口的关键字为
interface
,没有字段,所有的方法都是public abstract
。写不写都一样,疑问: 那么可不可以在接口里面定义一个protected方法?==》不可以
-
implements 关键字
接口不叫继承,叫实现
可以实现多个 interface
-
术语
java的接口特指
interface
定义,表示一个类或者一组方法的签名,而编程接口泛指接口规范,如方法签名,数据格式,网络协议等abstract class interface 继承 子类只能 extends 一个抽象父类 子类可以 implements
多个interface
字段 可以定义实例字段 不可以定义实例字段,可以定义静态字段 抽象方法 可以定义抽象方法 可以定义抽象方法 非抽象方法 可以定义非抽象方法 可以定义 default
方法 -
接口继承
一个
interface
可以继承自另一个interface
,接口之间的继承用到的是extends
,相当于是扩展了接口的方法interface Hello{ void hello(); } interface Person extends Hello{ void run(); String getName(); }
继承之后,
Person
相当于有3个抽象方法签名 -
1. 接口和抽象类 怎么样去相互继承 2. 公共逻辑具体是怎么样的 3. 上面这张图哪些是 接口 哪些是 抽象类
-
default 方法
接口中,可以定义
default
方法。default 方法需要有语句体实现类可以不必覆写
default
方法。default
方法的目的是,当我们需要给接口新增一个方法时,会涉及到修改全部子类。如果新增的是default
方法,那么子类就不必全部修改,只需要在需要覆写的地方去覆写新增方法。default
方法和抽象类的普通方法是有所不同的。因为interface
没有字段,default
方法无法访问字段,而抽象类的普通方法可以访问实例字段。
静态字段和静态方法
-
静态字段 static field
实例字段和静态字段的区别在于 静态字段是共享空间的,所以所有的修改都会直接影响静态字段的值
class Person{ public static int number; } // ??? 实例对象是否能直接访问 private(对象无法访问) 或 protected(可以)
-
对于静态字段,无论哪个实例对象对其修改,效果都是一样的,所有的实例的静态字段都会被修改,原因是因为静态字段并不属于实例
-
访问的方式:推荐使用 类名.静态字段 来访问
-
静态方法 static method
-
静态方法可以通过 类名.方法名() 直接调用,类似于其他编程语言的函数
class Main{ public static void main(String[] args){ Person.run(); } } class Person{ public static void run(){ System.out.println("run"); } }
-
由于静态方法其实是不属于实例对象的,所以静态方法内部无法使用
this
变量,他也无法访问实例字段,只能访问静态字段 -
所有基于实例变量调用的静态方法和静态字段都是编译器自动把实例变量改写为类名的
-
静态方法常用于工具类和辅助方法,例如
- Arrays.sort();
- Math.random();
-
接口不可以有实例字段,但是是可以有静态字段的,但是字段的类型必须是
public
static
final
类型,所以一般是省略修饰符的interface Person{ String name = "Unnamed"; // ===> public static final String name; int age = 18; // ===> public static final int age; }
interface PersonStaticFieldAndDefault{ String name = "Unnamed"; int age = 18; default void run(){ System.out.println("run" + name); } }