Java基础学习Day05
昨天陪女朋友啦 断更一天 今天开始恢复
今天主要学习了面向对象的内容
一、对象的概念
Java 是面向对象的编程语言,对象就是面向对象程序设计的核心。所谓对象就是真实世界中的实体,对象与实体是一一对应的,也就是说现实世界中每一个实体都是一个对象,它是一种具体的概念。对象有以下特点:
对象具有属性和行为。
对象具有变化的状态。
对象具有唯一性。
对象都是某个类别的实例。
一切皆为对象,真实世界中的所有事物都可以视为对象。
二、面向对象三大特征
1.封装
封装是将代码及其处理的数据绑定在一起的一种编程机制,该机制保证了程序和数据都不受外部干扰且不被误用。封装的目的在于保护信息,使用它的主要优点如下:
①保护类中的信息,它可以阻止在外部定义的代码随意访问内部代码和数据。
②隐藏细节信息,一些不需要程序员修改和使用的信息,比如取款机中的键盘,用户只需要知道按哪个键实现什么操作就可以,至于它内部是如何运行的,用户不需要知道。
③有助于建立各个系统之间的松耦合关系,提高系统的独立性。当一个系统的实现方式发生变化时,只要它的接口不变,就不会影响其他系统的使用。例如 U 盘,不管里面的存储方式怎么改变,只要 U 盘上的 USB 接口不变,就不会影响用户的正常操作。
④提高软件的复用率,降低成本。每个系统都是一个相对独立的整体,可以在不同的环境中得到使用。例如,一个 U 盘可以在多台电脑上使用。
2.继承
程序中的继承性是指子类拥有父类的全部特征和行为,这是类之间的一种关系。Java 只支持单继承。
3.多态
面向对象的多态性,即“一个接口,多个方法”。多态性体现在父类中定义的属性和方法被子类继承后,可以具有不同的属性或表现方式。多态性允许一个接口被多个同类使用,弥补了单继承的不足。
三、类和对象
在面向对象中,类和对象是最基本、最重要的组成单元。类实际上是表示一个客观世界某类群体的一些基本特征抽象。对象就是表示一个个具体的东西。所以说类是对象的抽象,对象是类的具体。
类是概念模型,定义对象的所有特性和所需的操作,对象是真实的模型,是一个具体的实体。由此可见,类是描述了一组有相同特性(属性)和相同行为(方法)的一组对象的集合。
类是构造面向对象程序的基本单位,是抽取了同类对象的共同属性和方法所形成的对象或实体的“模板”。而对象是现实世界中实体的描述,对象要创建才存在,有了对象才能对对象进行操作。类是对象的模板,对象是类的实例。
四、类的定义:
- 在 Java 中定义一个类,需要使用 class 关键字、一个自定义的类名和一对表示程序体的大括号。完整语法如下:
[public][abstract|final]class<class_name>[extends<class_name>][implements<interface_name>] {
// 定义属性部分
<property_type><property1>;
<property_type><property2>;
<property_type><property3>;
…
// 定义方法部分
function1();
function2();
function3();
…
}
2.在 Java 中类的成员变量定义了类的属性。声明成员变量的语法如下:
[public|protected|private][static][final]<type><variable_name>;
可以在声明成员变量的同时对其进行初始化,如果声明成员变量时没有对其初始化,则系统会使用默认值初始化成员变量。
声明成员方法可以定义类的行为,行为表示一个对象能够做的事情或者能够从一个对象取得的信息。类的各种功能操作都是用方法来实现的,属性只不过提供了相应的数据。
3 this关键字
this 关键字是 Java 常用的关键字,可用于任何实例方法内指向当前对象,也可指向对其调用当前方法的对象,或者在需要当前类型对象引用时使用。
①this.属性名:
大部分时候,普通方法访问其他方法、成员变量时无须使用 this 前缀,但如果方法里有个局部变量和成员变量同名,但程序又需要在该方法里访问这个被覆盖的成员变量,则必须使用 this 前缀。
②this.方法名
this 关键字最大的作用就是让类中一个方法,访问该类里的另一个方法或实例变量。
假设定义了一个 Dog 类,这个 Dog 对象的 run( ) 方法需要调用它的 jump( ) 方法,Dog 类的代码如下所示:
/**
* 第一种定义Dog类方法
**/
public class Dog {
// 定义一个jump()方法
public void jump() {
System.out.println("正在执行jump方法");
}
// 定义一个run()方法,run()方法需要借助jump()方法
public void run() {
Dog d = new Dog();
d.jump();
System.out.println("正在执行 run 方法");
}
}
使用这种方式来定义这个 Dog 类,确实可以实现在 run( ) 方法中调用 jump( ) 方法。下面再提供一个程序来创建 Dog 对象,并调用该对象的 run( ) 方法。
public class DogTest {
public static void main(String[] args) {
// 创建Dog对象
Dog dog = new Dog();
// 调用Dog对象的run()方法
dog.run();
}
}
在上面的程序中,一共产生了两个 Dog 对象,在 Dog 类的 run( ) 方法中,程序创建了一个 Dog 对象,并使用名为 d 的引用变量来指向该 Dog 对象。在 DogTest 的 main() 方法中,程序再次创建了一个 Dog 对象,并使用名为 dog 的引用变量来指向该 Dog 对象。
下面我们思考两个问题。
1)在 run( ) 方法中调用 jump( ) 方法时是否一定需要一个 Dog 对象?
答案是肯定的,因为没有使用 static 修饰的成员变量和方法都必须使用对象来调用。
2)是否一定需要重新创建一个 Dog 对象?
不一定,因为当程序调用 run( ) 方法时,一定会提供一个 Dog 对象,这样就可以直接使用这个已经存在的 Dog 对象,而无须重新创建新的 Dog 对象了。因此需要在 run() 方法中获得调用该方法的对象,通过 this 关键字就可以满足这个要求。
this 可以代表任何对象,当 this 出现在某个方法体中时,它所代表的对象是不确定的,但它的类型是确定的,它所代表的只能是当前类的实例。只有当这个方法被调用时,它所代表的对象才被确定下来,谁在调用这个方法,this 就代表谁。
将前面的 Dog 类的 run( ) 方法改为如下形式会更加合适,run( ) 方法代码修改如下,其它代码不变。
/**
* 第二种定义Dog类方法
**/
// 定义一个run()方法,run()方法需要借助jump()方法
public void run() {
// 使用this引用调用run()方法的对象
this.jump();
System.out.println("正在执行run方法");
}
③this( )访问构造方法
this( ) 用来访问本类的构造方法(构造方法是类的一种特殊方法,方法名称和类名相同,没有返回值。
public class Student {
String name;
// 无参构造方法(没有参数的构造方法)
public Student() {
this("张三");
}
// 有参构造方法
public Student(String name) {
this.name = name;
}
// 输出name和age
public void print() {
System.out.println("姓名:" + name);
}
public static void main(String[] args) {
Student stu = new Student();
stu.print();
}
}
注意:
①this( ) 不能在普通方法中使用,只能写在构造方法中。
②在构造方法中使用时,必须是第一条语句。
4.static
①static是一个修饰符,用于修饰类的成员方法、类的成员变量,另外可以编写static代码块来优化程序性能。
static修饰成员方法:
static修饰的方法一般称作静态方法,由于静态方法不依赖于任何对象就可以进行访问,因此对于静态方法来说,是没有this的,因为它不依附于任何对象,既然都没有对象,就谈不上this了。并且由于这个特性,在静态方法中不能访问类的非静态成员变量和非静态成员方法,因为非静态成员方法/变量都必须依赖具体的对象才能够被调用。
但是要注意的是,虽然在静态方法中不能访问非静态成员方法和非静态成员变量,但是在非静态成员方法中是可以访问静态成员方法/变量的。
因此,如果说想在不创建对象的情况下调用某个方法,就可以将这个方法设置为static。我们最常见的static方法就是main方法,至于为什么main方法必须是static的,现在就很清楚了。因为程序在执行main方法的时候没有创建任何对象,因此只有通过类名来访问。
②static修饰成员变量
static修饰的变量也称为静态变量,静态变量和非静态变量的区别是:静态变量被所有对象共享,在内存中只有一个副本,它当且仅当在类初次加载时会被初始化。而非静态变量是对象所拥有的,在创建对象的时候被初始化,存在多个副本,各个对象拥有的副本互不影响。
static成员变量的初始化顺序按照定义的顺序进行初始化。
③static修饰代码块
static关键字还有一个比较重要的作用就是用来形成静态代码块以优化程序性能。static块可以置于类中的任何地方,类中可以有多个static块。在类初次被加载的时候,会按照static块的顺序来依次执行每个static块,并且只会执行一次。
static块可以优化程序性能,是因为它的特性:只会在类被初次加载的时候执行一次。如下:
class Person{
private Date birthDate;
public Person(Date birthDate) {
this.birthDate = birthDate;
}
boolean isBornBoomer() {
Date startDate = Date.valueOf("1946");
Date endDate = Date.valueOf("1964");
return birthDate.compareTo(startDate)>=0 && birthDate.compareTo(endDate) < 0;
}
}
isBornBoomer是用来判断一个人是否是1946-1964年出生的,而每次isBornBoomer被调用的时候,都会生成startDate和birthDate两个对象,造成了空间浪费,如果改成下面这样效率会更高:
class Person {
private Date birthDate;
private static Date startDate, endDate;
static {
startDate = Date.valueOf("1946");
endDate = Date.valueOf("1964");
}
public Person(Date birthDate) {
this.birthDate = birthDate;
}
boolean isBornBoomer() {
return birthDate.compareTo(startDate) >= 0 && birthDate.compareTo(endDate) < 0;
}
}
5.构造方法:
①创建构造方法的规则:
构造方法的名字必须和类名一致
构造方法没有返回值,返回的是一个类
构造方法不能是抽象的,静态的,最终的,同步的也就是说,他不能通过abstract,static,final,synchronized关键字修饰
这个有点难理解,解释一下,就是由于构造方法是不能被继承的,所以final和abstract是没有意义的,而且他是用于初始化一个对象的,所以static也是没有用的,多个线程也不会同时去创建内存地址相同的对象,所以锁也是没有意义的
public class people {
private String name;
private int age;
//无参构造
public people() {
}
//有参构造方法
public people(String name, int age) {
this.name = name;
this.age = age;
}
}
②什么是默认构造方法
如果构造方法中没有参数,就说明他就是一个默认构造方法,也称为无参构造方法。
public class People {
private String name;
private int age;
//无参构造
public People() {
System.out.println("一个人的诞生");
}
//有参构造方法
public People(String name, int age) {
this.name = name;
this.age = age;
}
public static void main(String[] args) {
People people = new People();
}
}
③什么是有参构造
有参构造方法就肯定是由参数的构造方法。有了参数的话就可以为不同的对象供不同的值了。
public class People {
private String name;
private int age;
//无参构造
public People() {
System.out.println("一个人的诞生");
}
//有参构造方法
public People(String name, int age) {
this.name = name;
this.age = age;
}
public static void main(String[] args) {
People people = new People("张三",18);
People people2 = new People("小帅",2);
//姓名null年龄0
//System.out.println("姓名" + people.name + "年龄" + people.age);
}
}
④如何重载构造方法
构造方法和方法是类似的,他也可以重载。其重载的方法很简单,就是只要提供不一样的参数即可,编译器就会去通过不同的参数找到对应的构造方法
public class People {
private String name;
private int age;
//无参构造
public People() {
System.out.println("一个人的诞生");
}
//有参构造方法
public People(String name, int age) {
this.name = name;
this.age = age;
}
public People(String name) {
this.name = name;
}
public static void main(String[] args) {
People people = new People("张三",18);
People people2 = new People("小帅",2);
People people3 = new People("老王");
//姓名null年龄0
//System.out.println("姓名" + people.name + "年龄" + people.age);
}
}
五、继承:
1.概念:
继承的基本思想是,基于已有的类创造新的类。继承已存在的类就是复用这些类的方法,而且可以增加一些新的方法和字段,使新类能够适应新的情况,继承是Java程序设计中一项核心技术,它主要解决的问题是:共性的抽取,实现代码复用。
2.语法:
Java中表现继承的关系需要借助关键字extends:
修饰符 class 子类 extends 父类{
//...
}
注意:
子类将继承父类的成员变量和成员方法
子类继承父类之后,需要添加自己特有的成员,体现出与基类的不同
3.父类成员访问
(1)子类访问父类的成员变量
如果访问的成员变量子类中有,则优先访问子类本身的
如果访问的成员变量子类中无,父类中有,则访问继承下来的
如果子类与父类中有同名的成员变量,则优先访问子类自己的,即子类将父类的同名变量隐藏
成员变量的访问遵循就近原则,自己有就优先访问自己的
(2)子类中访问父类的成员方法
通过子类访问成员方法,先看子类本身有没有,如果有访问自己的,如果没有,访问父类的
通过子类访问与父类同名方法时,如果子类和父类方法的参数列表不同则构成重载,根据调用方法传递的参数选择合适的方法访问
如果子类和父类同名方法的原型一致,则只能访问到子类的
4.super关键字
如果子类中存在与父类同名的方法成员,则通过关键字super在子类方法中访问父类方法成员
public class Base {
int a;
int b;
int c;
public void method1() {
System.out.println("我是父类方法");
}
public void method2() {
System.out.println("我是父类方法");
}
}
public class Derived extends Base {
int a;
int b;
int c;
public void method1(int n){
System.out.println("我是子类方法");
}
public void method2(){
System.out.println("我是子类方法");
}
public void method(){
a = 10;
b = 20; //访问子类成员
c = 30;
super.a = 10;
super.b = 20; //访问父类成员
super.c = 30;
method1(); //访问父类方法
method1(10); //访问子类方法
method2(); //访问子类方法
super.method2();//访问父类方法
}
}
注意:只能在非静态方法中使用
5.子类构造方法
构造哪个类的对象,就调用哪个类的构造方法,调用构造方法时先调用基类,在调用子类,即在子类中隐藏super()
public class Base {
public Base(){
System.out.println("Base()");
}
}
public class Derived extends Base {
public Derived(){
System.out.println("Derived()");
}
}
public class Text {
public static void main(String[] args) {
Derived d = new Derived();
}
}
输出结果:
Base()
Derived()
在子类构造方法中,并没有写任何关于基类构造的代码,但是在构造子类对象时,先执行基类的构造方法,然后执行子类的构造方法
①若父类显示定义无参或者默认的构造方法,在子类构造方法的第一行默认有隐含的super调用,即调用基类的构造方法
②如果父类的构造方法是带有参数的,此时编译器不会给子类生成默认的构造方法,此时需要用户在子类中显示定义构造方法,并在子类构造方法中选取合适的父类构造方法调用
③在子类构造方法中,super(…)调用父类构造时,必须是子类构造方法中的第一条语句
④super(…)只能在子类的构造方法中出现一次,并不能和this同时出现
6.执行顺序
(1)无继承关系时的执行顺序:
静态代码块先执行,且只执行一次,在类加载阶段执行
当有对象创建时,才会执行实例代码块,实例代码块执行完后,再执行构造方法
(2)有继承关系时的执行顺序:
父类静态代码块优先子类静态代码块执行,都是最早执行的
父类实例代码块和父类构造方法紧接着执行
子类的实例代码块和子类构造方法在接着执行
第二次实例化对象时,父类和子类的静态代码块都不会在执行
7.继承方式
8.final关键字
①修饰类,表示类不能继承:
final public class Person{
}
public class Student extends Person{
}//编译报错
②修饰方法,表示方法不能被重写
9.继承的好处和弊端
好处:
提高了代码的复用性
提高了代码的维护性
让类与类之间产生了关系,是多态的前提
弊端:
继承是侵入性的
降低了代码的灵活性
继承关系,导致子类必须拥有父类非私有属性的方法,让子类自由的世界多了些约束
增强了代码的耦合性
六、多态
1.多态的概念
通俗来说,就是多种形态,那么在Java中,就是去完成某个行为,当不同的对象去完成时会产生不同的状态和表现。
总的来说:同一件事,发生在不同对象身上,就会产生不同的结果
2.多态实现条件
在Java中要实现多态,那么必须要满足以下几个条件,缺一不可:
必须在继承体系下
子类必须要对父类中的方法进行重写
通过父类的引用调用重写的方法
多态体现:在代码运行时,当传递不同类对象时,会调用对应类中的方法。
public class Animal {
String name;
int age;
public Animal(String name,int age){
this.name = name;
this.age = age;
}
public void eat(){
System.out.println(name+"吃饭");
}
}
public class Cat extends Animal{
public Cat(String name, int age) {
super(name, age);
}
@Override
public void eat() {
System.out.println(name+"吃鱼");
}
}
public class Dog extends Animal{
public Dog(String name, int age) {
super(name, age);
}
@Override
public void eat() {
System.out.println(name+"吃骨头");
}
}
/分割线/
public class TestAnimal {
//编译器在编译代码的时候,并不知道要调用Dog还是Cat中eat的方法
//等程序运行起来之后,形参a引用的具体对象确定后,才知道调用哪个方法
//此时要注意:此处的形参类型必须是父类类型才可以,也就是向上转型
public static void eat(Animal animal){
animal.eat();
}
public static void main(String[] args){
Cat cat = new Cat("元宝",2);
Dog dog = new Dog("小七",1);
eat(cat);
eat(dog);
}
}
在上述代码中,分割线上方的代码是类的实现者 编写的,分割线下方的代码是类的调用者编写的。当类的调用者在编写eat();这个方法的时候,参数类型为Animal(父类),此时在该方法内部并不知道,也并不关注当前的animal引用指向的是哪个类型(哪个子类)的实例。此时animal这个引用调用eat方法可能会又多种不同的表现(和animal引用的实例对象相关),这种行为就叫做多态。
3.重写
也称为覆盖。重写是子类对父类非静态、非private修饰,非final修饰,非构造方法等进行重新编写的一个过程,返回值和形参都不能改变。即外壳不改变,核心重写。
重写的好处在于子类可以根据需要,定义特定的属于子类自己的行为。也就是说子类能够根据需要来实现父类的方法,又和父类的方法不完全一样,实现自己的特色。
方法重写的规则:
①子类在重写父类的方法时,一般必须与父类方法原型一致:修饰符 返回值类型 方法名(参数列表)要完全一致
②JDK7以后,被重写的方法返回值类型可以不同,但是必须是具有父子关系的
③访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类方法被public修饰,则子类中重写该方法就不能声明为protected
④父类被static、private修饰的方法都不能被重写
⑤子类和父类在同一个包中,那么子类可以重写父类中的所有方法,除了声明为private和final的方法
⑥子类和父类不在同一个包中,那么子类只能够重写父类的 声明为public 和protected的非final方法
⑦重写的方法,可以使用 @Override 注解来显式指定。有了这个注解能够帮我们检查这个方法有没有被正确重写。例如不小心讲方法名拼写错了,此时编译器就会发现父类中并没有这个方法,就会编译报错,构不成重写。
重写和重载的区别:
区别点 重载(overloading) 重写(override)
参数列表 必须修改 一定不能修改
返回类型 可以修改 一定不能修改
访问限定符 可以修改 不能做出更严格的限制(子类权限大于父类)
4.向上转型和向下转型
(1)向上转型:实际上就是创建一个子类对象,将其当成父类对象来舒勇
语法格式: 父类类型 对象名 = new 子类类型();
子类向上转型为父类
如果子类中有重写父类方法,父类对象默认调用子类中重写方法
注意 :
父类可以直接访问子类重写方法。
父类不能直接访问子类私有方法、子类所有属性。
父类可以通过重写方法调用子类私有方法和属性
父类重写方法中调用方法和属性顺序:子类 -> 父类
(2)向下转型:
注意: 只有向上转型过的对象才能向下转型
向下转型为恢复子类所有功能。
向下转型用的比较少,而且不安全,万一转换失败,运行时就会抛出异常。Java中为了提高向下转型的安全性,引入了instanceof,如果该表达式为true,则可以安全转换
5.多态的优缺点
优点:
①能够降低代码的“圈复杂度”,避免使用大量的if-else
②可扩展能力更强
缺点:
代码运行的效率降低
明天会更好