1、封装
在类中定义属性的时候,一般需要把属性隐藏起来。
如果外界需要访问这个属性,那么就提供公共方法对其访问
封装就是将一些属性,特征等封装进一个类中,可以通过调用公共的方法来使用这些属性,特征等。
我们在定义一个类的时候,就是在完成封装的过程,将属性,方法定义在类中
封装的特点
- 提高的代码的安全性,重要的信息可以私有化,不会让其他类直接访问这些信息,不对外暴露这些信息
- 提高代码的复用性,常用的代码或者功能封装到方法中,可以在其他地方通过访问该方法来获取这些代码或功能
- 封装代码的实现细节,以便修改内部带啊吗,提高维护性
- 简化外部的调用,便于调用者使用
2、方法重载
类中共有多个方法,具有相同的方法名,但是方法的参数各不相同,这种情况被称为方法的重载
public void test(){
}
public void test(int a){
}
方法的重载要求
- 方法的重载必须是在同一类中
- 方法的名字必须相同
- 方法的参数列表必须不同(参数列表---->参数类型,参数类型的排序,参数个数)
- 方法的修饰符,返回类型,抛出异常这些地方没有限制(可以相同,也可以不相同,一般习惯性都是相同的)
例如,
public class Test{
//参数的个数不同
public void test(){}
public void test(int a){}
public void test(String s1,String s2){}
//参数的类型不同
public void test(boolean flag){}
public void test(double salary){}
//参数的顺序不同
public void test(int a,String s){}
public void test(String s,int a){}
//方法的参数可以相同,也可以不同
public int test(int a,int b){
return 0;
}
}
特殊情况:
public class Test{
//方法重载
public void test(int a,long b){}
public void test(long b,int a){}
public static void main(String[] args){
Test t = new Test();
t.test(1,1L);//调用到第一个方法
t.test(1L,1);//调用到第二个方法
t.test(1,1);
}
}
注意,这时候,
t.test(1,1)
代码是会编译报错的,因为类中定义的俩个方法都匹配注意,当参数无法完全精确匹配到方法的时候,参数会尝试做自动转换,然后再去尝试匹配方法,在t.test(1,1)中,当后面的1去尝试转化为long类型的时候,前面的1也会跟着转换,这就会导致无法匹配与参数相对应的方法。
例如,
public class Test {
//重载方法1
public void test(int a){}
//重载方法2
public void test(short a){}
public static void main(String[] args){
Test t = new Test();
byte a = 1;
t.test(a);//这里会调用第二个方法,也就是short类型参数的test方法
}
}
虽然byte类型数据,可以自动转换为short,也可以转换为int,但是short离byte“更近”,就近原则
3、创建和初始化对象
new Test();这句代码完成了两过程,对象的创建和初始化
-
new关键字,给对象申请/分配内存空间,并将对象中的属性进行默认值的初始化,根据属性类型不同,其默认值分别为:整型(0) 、浮点型(0.0)、char(’\u0000’) 、引用数据类型(null)、boolean(false)
-
如果在代码中,还给这些代码进行了显示赋值,那么就会用这个显示赋值,把之前的默认值给覆盖掉
private String name = "凉拌小番茄"; //原本在创建对象时候,会给name赋一个默认值null,然后我们手动给那么显示赋值,那么此时name的值就变成了 “凉拌小番茄”
-
调用类中的构造器,在构造器中也可以对属性进行赋值,这个赋值会把之前默认值或者显示赋值给覆盖掉(构造器是在创建对象中最后实现的)
注意,
这时候对象已经创建出来了,并且属性也有了值(可能是默认值,也可以是显示赋值,也可能构造器中赋的值),如果我们想再设置其他的属性值,可以使用对象访问属性,或者调用方法。
如果是public属性,那么就可以直接使用对象进行访问并赋值。(不推荐,安全性不高)
如果是private属性,那么就需要使用对应的setXxx方法进行属性值的的设置。(推荐)
private String name;
public void setName(String name){
this.name = name;
}
public String getName(){
return name;
}
这里介绍private修饰符的特点
- private是四种修饰符( public,protected,default,private)中的一种,并且是权限最小的一种
- private 可以修饰成员变量和成员方法
- private修饰成的属性和方法,就只在当前类中才能访问,当前类意外的其他地方不可以直接访问
- private修饰的属性称为私有属性,private修饰的方法称为类中的私有方法
局部变量和成员变量有什么区别
- 出现的位置不同
- 成员变量直接出现在类中
- 局部变量定义在方法中或者代码块中
- 初始化
- 成员变量会默认初始化(默认值)
- 局部变量必须要手动进行赋值
- 生命周期
- 成员变量随着对象的创建而初始化,随着对象的回收而消失,成员变量的生命周期跟对象是一致的
- 局部变量随着方法的栈帧入栈而初始化,随着方法的栈帧而消失,生命周期与方法生命周期一致
4、构造器
类中的构造器也称为构造方法,构造函数,是在创建对象的时候必须要调用的方法
因为创建对象会必须要调用构造方法,所以有一个隐藏的,Java提供的默认构造方法
public class Test{
new Test;
public Test(){
//这就是Java默认提供的构造方法,这个构造器中不执行任何代码。称为无参构造器
}
}
构造器的特点:
- 必须与类名保持一致
- 必须没有返回类型,也不能写void
构造器的作用:
- 使用new关键字来创建对象的时候,后面跟着必须是类中存在的构造器
- 构造器中的代码,在对象创建之后会被调用,从而可以完成对象的初始化工作
public class Student{
private String name;
private int age;
//构造器
public Student(String name,int age){
this.name = name;
this.age = age;
}
public static void main(String[] args){
//创建对象的时候使用构造器
//并且传参到构造器中,构造器中可以使用这些参数给属性进行初始化
Student stu = new Student("tom",20);
}
构造器的重载:
除了默认的无参构造器之外,在类中还可以对构造器进行重载,让构造器可以接收一些参数,然后使用这些参数进行对象的初始化工作。
public class Student{
private String name;
private int age;
//无参构造器
public Student(){}
//有参数构造器,接收参数,进行对象属性的初始化
public Student(String name){
this.name = name;
}
public Student(int age){
this.age = age;
}
public Student(String name,int age){
this.name = name;
this.age = age;
}
}
注意构造方法重载之后,或者自己在类中定义了一个构造器时,系统就不会在提供一个默认无参构造器,我们要在类中自己手写一个无参构造器
构造器之间的调用:
使用this关键字,可以在构造器中,调用另一个构造器
public class Student{
private String name;
private int age;
//无参构造器
public Student(){
//调用俩参的构造器
this("tom",20);
}
public Student(String name){
//调用俩参的构造器
this(name,0)
}
public Student(int age){
//调用俩参的构造器
this(null,age);
}
public Student(String name,int age){
this.name = name;
this.age = age;
}
}
注意在一个构造器中无法同时使用两个this调用其他构造器,这是因为this默认调用的就是这个对象,必须要放在方法的第一行,所以只能存在一个,但类似于this.name,this.age可以同时使用是因为,调用的是属性,而不是方法。
5、this
this代表,所在类的当前对象的引用(地址值),即对象自己的引用。
public class Student{
public void sayHello(){
this.show();//这个this就表示当前类的对象stu
}
public void show(){
}
}
public static void main(String[] args){
Student stu = new Student();
stu.sayHello();
}
在Student类中定义了两个方法,要想调用这俩个方法,就需要创建Student的对象,然后通过这个对象来调用这两个方法
注意类中的非静态方法,需要通过类的对象来调用,没有其他方式,而静态方法可以直接调用(static修饰的方法)
在这一种情况之下,我们要在sayHello方法中调用show方法就需要通过this.show()方法
这里的this代表的就是调用当前类的对象地址,通过地址来操作对象
注意,当我们创建两个对象的时候
每一个对象中,都有自己的this,和其他对象中的互不影响。
当前执行stu1.sayHello()代码的时候,this代表的就是stu1
当前执行stu2.sayHello()代码的时候,this代表的就是stu2
方法被哪个对象调用,方法中的this就代表那个对象。即谁在调用,this就代表谁。
this.name访问的是当前类中name
一个对象的创建
- new 分配内存(堆的新生代的edan)并且将成员变量默认初始化
- 如果属性有显示初始化(手动赋值),于是于是就会将数据的默认值覆盖掉
- 调用合适的构造方法,构造方法完成就意味着对象创建完毕
- 将对象的地址赋值给相应的变量
6、继承
继承描述的是事物的所属关系,子类继承父类,那么子类就可以继承父类中定义的非私有属性和方法
父类更为通用,子类更为具体(这就好比多个儿子拥有同一个父亲,父亲通用为所有儿子,但每一个儿子都会继承父亲的一些特征,习惯,但每一个儿子各不相同,并且有了属于自己的特征,习惯)
-
多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么这些类(子类)就不需要再定义这些属性和行为,只要继承同一个类(父类),它们就可以直接访问父类中的非私有的属性和方法。
-
继承的好处
- 提高代码的复用性
- 类与类之间产生了关系,这是使用多态特性的前提
-
继承的关键字----extends
//定义Person类,作为父类 public class Person{ String name; public void sayHello(){ System.out.println("hello~ I am "+name); } } //定义Student类,作为子类 class Student extends Person{ } public static void main(String[] args){ Student stu = new Student(); stu.name = "tom";//name属性从父类中继承而来 stu.sayHello();//sayHello方法从父类中继承而来 }
-
子类继承父类,继承了父类中的属性和方法,并可以在子类中访问这些属性和方法:
//定义Person类,作为父类 public class Person{ String name; private int age; public void sayHello(){ System.out.println("hello~ I am "+name); } } //定义Student类,作为子类 class Student extends Person{ //这个是子类中单独定义的方法,和父类无关 public void hi(){ name = "jack";//访问从父类中继承过来的属性 sayHello();//调用从父类中继承过来的方法 age = 20;//编译报错,age是父类中的私有属性,子类中不能访问 } }
父类中的构造器,子类是不能继承的
java中,类和类之间的继承是单继承,一个类只能有且只有一个父类,不能同时直接继承俩个父类,但是可以间接继承其他类(子类继承父类,父类继承自己的父类,子类也会有自己父类的父类的方法)
java中,如果没有给类指定父类的话,那么这个类会默认继承父类Object
Object类是Java提供的类,每一个类都是直接或者间接继承了Object类。
子类继承父类,创建子类对象的时候,会先默认调用父类的构造器:
//定义Person类,作为父类
public class Person{
public Person(){
System.out.println("Person类中的构造器被调用");
}
}
//定义Student类,作为子类
class Student extends Person{
public Student(){
System.out.println("Student类中的构造器被调用");
}
}
public static void main(String[] args){
new Student();
}
//main方法执行会输出以下语句:
Person类中的构造器被调用
Student类中的构造器被调用
子类继承父类,会继承父类的属性和方法,那么就需要先调用父类的构造器对父类中的属性进行初始化,初始化完成后再给子类使用。
构造器的作用之一就是进行初始化
7、super关键字
在类中,除了可以使用this关键字之外,还可以使用super关键字
在子类中,使用super关键字一般做以下事情:
-
访问父类中的属性
-
调用父类中的方法
-
调用父类中的构造器
访问父类中的属性:
public class Person{
String name = "zs";
}
public class Student extends Person{
//注意,这里是故意和父类中的属性重名
String name = "lisi";
public void test(){
//直接使用name,表示Student中的name属性(就近原则)
System.out.println(name);
//也可以使用this和super来区分访问的是哪个name
System.out.println(this.name);//Student中的name
System.out.println(super.name);//父类Person中的name
}
}
调用父类中的方法:
public class Person{
public void run(){
System.out.println("person run..");
}
}
public class Student extends Person{
//注意,这里是故意和父类中的方法重名
public void run(){
System.out.println("student run..");
}
public void test(){
//直接使用name,表示Student中的run方法(就近原则)
run();
//也可以使用this和super来区分调用的是哪个run方法
this.run();
super.run();
}
}
调用父类中的构造器:
子类构造器中隐式调用父类无参构造器,例如
public class Person{
public Person(){
}
}
public class Student extends Person{
public Student(){
//这里会隐式调用(自动调用)父类的无参构造器
}
}
子类构造器中显式调用父类无参构造器,例如
public class Person{
public Person(){
}
}
public class Student extends Person{
public Student(){
//也可以使用super关键字,显示调用父类的构造器(有参的无参的都可以调用)
super();
}
}
子类构造器中显式调用父类有参构造器,例如
public class Person{
public Person(){
}
public Person(String name){
}
}
public class Student extends Person{
public Student(){
//也可以使用super关键字,显示调用父类的构造器(有参的无参的都可以调用)
super("tom");
}
}
在构造器中,可以使用this调用类中其他构造器,也可以使用super调用父类中的构造器。
但是this和super这俩中调用构造器的代码,不能同时出现,否则会报错。
这是因为this调用构造器的语句和super调用构造器的语句,都要求自己是第一句代码,但是构造器中的第一句代码只能有一个,所以它们俩个不能同时出现,例如
public class Person{
public Person(){
}
}
public class Student extends Person{
//编译报错,在使用this和super调用构造器功能的时候,它们俩个不能同时出现
public Student(){
this("tom");
super();
}
public Student(String name){
}
}
但是,如果this和super不是都要调用构造器,那么同时出现就没有问题,例如
public class Person{
public void sayHello(){
}
}
public class Student extends Person{
//编译通过
public Student(){
this("tom");
super.sayHello();
}
public Student(String name){
}
}
思考,在子类的构造器中,为什么要调用父类的构造器?
**如果你生成子类对象时没有调用父类的构造器,那么,我们在使用父类的一些成员变量的时候,就会报变量未初始化的错误。**请记住,变量初始化总是在构造器调用之前完成!
构造造一个对象,先调用其构造方法,来初始化其成员函数和成员变量。子类拥有父的成员变量和成员方法,如果不调用,则从父类继承而来的成员变量和成员方法得不到正确的初始化。
8、方法重写
如果子类和父类中出现了相同的方法,这种情况就叫做方法重写 (Override)。
注意,方法重载是发生在同一个类中,方法重写发生在子父类之间
父类中的一个方法和子类中的一个方法,满足以下要求,就是方法的重写:
-
方法名必须相同
-
参数列表必须相同
-
访问控制修饰符可以被扩大,但是不能被缩小
public > protected > default > private
-
方法抛出异常类型的范围可以被缩小,但是不能被扩大
例如:ClassNotFoundException --扩大–> Exception
例如:Exception --缩小–> ClassNotFoundException
-
返回类型可以相同,也可以不同
-
如果父类的返回类型是引用类型,子类重写后的方法返回类型可以和父类方法的返回类型保持一致,也可以是父类方法返回类型的子类型
例如,父类方法的返回类型是Person,子类重写后的返回类可以是Person也可以是Person的子类型
-
如果父类的返回类型是基本类型,那么子类重写后的返回类型必须和父类的保持一致
例如: 父类方法的返回类型是int,子类重写后的返回类也必须是int
-
注意,大多数情况下,子类中重写的方法 会和 父类中的方法 完全保持一致,只有方法的实现不同。(也就是大括号中代码不一样)
方法重写和重载的区别的是什么?
overload
- 发生在本类中
- 方法名相同 只跟参数列表有关 跟其他的无关
overwirte
- 发生子父类之间
- 方法名与父类方法相同,参数列表与父类方法名相同
- 重写的方法修饰符要么与父类方法相同或者比父类方法大
- 抛出的异常要么与父类相同要么比父类的方法小
- 重写方法返回值基本数据类型保持一致
- 如果是引用类型 要么保持一致要么就是该类型的子类
父类中哪些方法不能被重写:
- 父类中的静态方法不能被子类重写
- 父类中的私有方法不能被子类重写
也就是说,只有在子类中可以直接访问到的父类方法,并且是非静态的方法,才能被子类重写
常见的重写情况,例如
public class Person{
public void sayHello(){
System.out.println("你好!很高兴认识你");
}
}
public class Student extends Person{
//子类中,重写父类的sayHello方法
public void sayHello(){
System.out.println("hello!nice to meet you");
}
}
public static void main(String[] args) {
Student stu = new Student();
//由于子类重写了sayHello方法,所以这里将会调用到子类中重写后的sayHello
//如果子类中没有重写sayHello方法,那么这里将会调用到从父类继承过来的sayHello方法
stu.sayHello();
}
子类继承父类,在调用方法的时候,如果子类中没用重写,那么调用的时候从父类继承的方法,如果子类重写了这个方法,那么将会调用到子类中重写后的方法。(非常重要)
在JavaAPI中,String类继承了Object,并且重写了Object类中的toString、equals等方法:
注意,Object所所有类的父类,每一个类都是直接或间接继承了Object类
所以,在调用String对象的toString、equals等方法时候,其实调用的是子类String中重写后的方法!
思考,一般什么情况下,我们会在子类中,对从父类继承的方法进行重写?
子类继承父类,继承了父类中的方法,但是父类中的方法并不一定能满足子类中的功能需要,所以子类中需要把方法进行重写。
toString方法
toString方法如果重写,打印对象就是打印toString方法里面的内容
联系,下面结果为多少
class A {
public void say() {
System.out.println("A");
}
public A() {
this.say();
}
}
public class AStudent extends A {
public AStudent() {
this.say();
}
public void say() {
System.out.println("B");
}
public static void main(String[] args) {
new AStudent();
}
}