Java——类和对象
1. 类
-
类是一个类型事物的抽象,是抽象的,不是具体的。而某一类事物总会有各种各样的特征,这些就是类的属性
-
类的组成 = 类的属性 + 类的方法 + 类的构造函数
-
类在使用前必须声明,才可以声明变量,创建对象
-
类的声明:
[标识符 //public、private、protected] class [类的名称]{
//零到多个类的属性
//零到多个类的方法
//一到多个类的构造函数
}
-
标识符
- public:public是公共的意思,被public修饰的成员可以被所有类访问
- protected:protected修饰的成员既可以被同一个包中的其他类访问,也可以被不同包中的子类访问
- default:如果在类的成员前没有任何访问控制符,则该成员处于默认访问状态。处于默认访问状态的成员,只能被同一个包中的其它类访问
- private:private修饰的成员只能被这个类本身访问,其它类(包括同一个包中的类、其他包中的类和子类)都无法访问private修饰的成员
-
一个类可以包含以下类型变量:
- 局部变量:在方法、构造方法或语句块中定义的变量为局部变量。变量声明和初始化都是在方法中,方法结束后,变量会自动销毁
- 成员变量:成员变量是定义在类中,方法体之外的变量。这种变量在创建对象的时候实例化。成员变量可以被类中方法、构造方法和特定类的语句块访问
// 类中各成员之中可相互调用,但需要注意static修饰的成员不能访问没有static修饰的成员。 - 类变量:类变量也声明在类中,方法体之外,但必须声明static类型
-
类的定义
- 类的属性定义:
// 数据类型 属性; String name;
- 类的方法定义:
返回值的数据类型 方法名称(参数1,参数2…){
程序语句 ;
} - 类的构造方法定义:
(1)每个类都有构造方法,其作用默认是创建对象
(2)如果没有显式的为类定义构造方法,编译器会为该类提供一个默认的构造方法;若创建了一个构造函数,编译器就会把默认的构造方法注释掉
(3)若想保留默认的构造方法,又想重新定义新的构造方法,则需要两个都保留
(4)创建一个对象的时候,至少要调用一个构造方法
(5)构造方法的名称必须与类同名,并且不能有返回类型,一个类可以有多个构造方法
(6)// 类名称(参数,…){ //零个到多个构造方法 }
- 类的属性定义:
-
类的使用
- 类使用必须要实例化,即创建对应的对象
- 使用格式:类名(数据类型) 变量名=new 类名();
- 访问对象中的成员:变量名.成员
2. 对象
- 对象是类的一个实体,实例,是具体的,不是抽象的
- 类生成对象的过程叫实例化
- 声明:声明一个对象,包括对象名称和对象类型
- 实例化:用关键字new创建一个对象
- 初始化:使用new创建对象时,会调用构造方法初始化对象
Java——类的封装
-
什么是类的封装?
类的封装是指将对象内部的信息隐藏,不允许外部直接访问对象内部的状态信息,只有通过该类对外提供的方法才能实现对内部信息的操作和访问 -
封装有什么优点?
- 隐藏类的实现细节
- 只能通过对外提供的方法进行数据的访问,限制对成员变量的不正当存取
- 降低耦合性,提高了代码的可维护性
- 一个对象中,成员变量是核心所在,反映对象的外部特征,一般不允许外部对象对其直接访问。
-
封装成员变量
1. why?:实现一个类时,公有数据或者不加保护的数据是非常危险的,所以一般情况下应该将数据设为私有,然后通过方法操作数据
2. 封装前的成员变量示例:
// 未封装Person类的成员变量
public class Person {
String name;
int age;
}
// 可直接被外部访问对象数据
public class hello {
public static void main(String args[]) {
Person p1 = new Person();
p1.age = 18;
p1.name = "csdn";
System.out.println("name:"+p1.name);
System.out.println("age:"+p1.age);
}
}
- 封装方法:使用private关键字将成员变量私有化,在构造相应的访问方法,这样外部就只能通过方法来访问对象信息
// 封装后
public class Person {
private String name;
private int age;
public String getName() { // **封装所需的getter和setter方法,可通过快捷键生成**
return name; // **alt + Insert ->选择getter和setter选项 ->ctrl + A 全选 ->回车即可**
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
// 只能通过方法访问类的成员变量
public class hello {
public static void main(String args[]) {
Person p1 = new Person();
p1.setAge(18);
p1.setName("csdn");
System.out.println("name:"+p1.getName());
System.out.println("age:"+p1.getAge());
}
}
-
封装方法
- why?:方法一般情况下都设计成公有的,根据需要设计成私有的,私有方法不会被外部类操作,既保证了安全性,有能根据需要删除。如果希望子类来重写父类的一个方法,此时用protected修饰。
-
单例模式的封装
- 什么是单例类?
单例类就是指这个类,指创建一个实例对象,单例设计模式能够保证一个类只创建一个实例,节省了因重复创建对象所带来的内存消耗,从而提高效率。 - 非单例类有什么不好?
类的构造器大多数时候定义成public的,允许任何类自由创建该类的对象,但是在某些时候,允许其他类自由创建该类的对象没有任何意义,还可能因为频繁的创建以及回收对象造成系统性能下降 - 如何封装?
该类的构造器用private修饰,把构造器私有化,那么我们就需要向外部提供一个静态的方法(因为调用该方法前不存在对象,因此它只能是静态的)来获取一个对象实例。 - 代码示例:
- 什么是单例类?
class Singleton {
private static Singleton s;
// 封装
private Singleton() {
}
public static Singleton getInstance() {
if (s == null) {
s = new Singleton();
}
return s;
}
}
public class TestSingleton {
public static void main(String[] args) {
Singleton s1 = Singleton.getInstance();
Singleton s2 = Singleton.getInstance();
System.out.println(s1==s2);
}
}
Java——this关键字(3种用法)
- this.属性名
- this.调用本类属性,也就是类中的成员变量;用来区分成员变量和局部变量(重名问题)
- 代码示例:
public class Person {
private String name;
private int 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;
}
}
-
通常使用{ }括起来的一个数据块叫做一个作用域范围。在同一个作用域范围定义了两个同名的变量则会报错;若是在该作用域定义了一个与该作用域范围之外同名的变量则是可以的,在这个作用域同时使用这两个变量,则在这个作用域中定义的变量会屏蔽作用域范围之外的那个变量,即作用域中定义的变量才会有效。
在同一作用域内,作用范围小的变量会屏蔽同名的作用范围大的变量。(就近原则) -
this.方法名
- this.调用方法;让类中一个方法,访问该类里的另一个成员方法
- 示例:定义了一个 Dog 类,这个 Dog 对象的 run( ) 方法需要调用它的 jump( ) 方法
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( ) 方法
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 对象。但是否一定需要重新创建一个 Dog 对象?
不一定,因为当程序调用 run( ) 方法时,一定会提供一个 Dog 对象,这样就可以直接使用这个已经存在的 Dog 对象,而无须重新创建新的 Dog 对象了。因此需要在 run() 方法中获得调用该方法的对象,通过 this 关键字就可以满足这个要求。
- 修改后代码:
public void run() {
// 使用this引用调用run()方法的对象
this.jump();
System.out.println("正在执行run方法");
}
-
this 可以代表任何对象,当 this 出现在某个方法体中时,它所代表的对象是不确定的,但它的类型是确定的,它所代表的只能是当前类的实例。只有当这个方法被调用时,它所代表的对象才被确定下来,谁在调用这个方法,this 就代表谁。
-
注意:对于 static 修饰的方法而言,可以使用类来直接调用该方法,如果在 static 修饰的方法中使用 this 关键字,则这个关键字就无法指向合适的对象。所以,static 修饰的方法中不能使用 this 引用。并且 Java 语法规定,静态成员不能直接访问非静态成员。
-
this( )访问构造方法
- this( ) 不能在普通方法中使用,普通方法中不可以调用构造函数,因此只能写在构造方法中。
- 在构造函数中调用普通方法,一般不推荐调用在方法内部使用了属性的普通方法。因为构造函数还没有执行完,属性没有被完全初始化,这时使用属性可能会出现异常。
- 构造函数之间可以相互调用,构造函数之间相互调用就一定需要使用this关键字!
- 使用this():表示调用无参构造函数
- this(参数,…):表示调用有参构造函数
- 在构造方法中使用时,必须是第一条语句
- 代码示例:
public class Student{
private String name = null;
private int age = 0;
public Student() {
System.out.println("Student():name="+name+",age="+age);
}
public Student(String name, int age) {
this();
this.name = name;
this.age = age;
System.out.println("Student(String name, int age):name="+name+",age="+age);
}
public static void main(String[] args) {
Student s = new Student("小明",20);
}
}
Java——final关键字
-
修饰的变量称为常量,值不能被修改。 (final int a = 10;)
-
修饰的方法不能被重写,保证安全性和稳定性。
-
修饰的类不能被继承,避免功能被覆盖。
Java——方法重载
-
什么是方法重载?
我们可以在同一个类中创建多个同名的方法,并且所有的方法以不同的形式工作 -
必须满足的条件:
- 方法名字相同
- 方法类型不同 or 传入参数不同
Java——继承
-
什么是继承?
继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法,或子类从父类继承方法,使得子类具有父类相同的行为。 -
为什么需要继承?
继承提高了类之间的耦合性,但耦合度高就会造成代码之间的联系越紧密,代码独立性越差 -
继承的特点
- 子类拥有父类非 private 的属性、方法,子类无法继承父类中私有的内容
- 子类可以拥有自己的属性和方法,即子类可以对父类进行扩展
- 子类可以用自己的方式实现父类的方法(重写)
- Java 的继承是单继承,但是可以多重(多层)继承,不能多继承(即一个子类同时继承多个父类),因为多继承容易出现问题。两个父类中有相同的方法,子类到底要执行哪一个是不确定的。
- 所有的类都是继承于 java.lang.Object,当一个类没有继承的两个关键字,则默认继承object祖先类
-
继承的实现(2种方法)
- extend 关键字
class 父类 {
}
class 子类 extends 父类 {
}
- implements 关键字:可以变相的使java具有多继承的特性,使用范围为类继承接口的情况,可以同时继承多个接口(接口跟接口之间采用逗号分隔)
public interface A {
public void eat();
public void sleep();
}
public interface B {
public void show();
}
public class C implements A,B {
}
- 方法重写
- 在继承关系中,子类会自动继承父类中定义的方法,但有时在子类中需要对继承的方法进行一些修改,即对父类的方法进行重写。需要注意的是,在子类中重写的方法需要和父类被重写的方法具有相同的方法名、参数列表以及返回值类型
- 当子父类中出现成员函数一模一样的情况,会运行子类的函数。这种现象,称为覆盖操作,这是函数在子父类中的特性,覆盖也称为重写
- 当子类需要父类的功能,而功能主体子类有自己特有内容时,可以复写父类中的方法,这样,即沿袭了父类的功能,又定义了子类特有的内容。
- 父类中的私有方法不可以被覆盖
- 父类为static的方法无法覆盖
- 覆盖时,子类方法权限一定要大于等于父类方法权限
// 父类
public class Animal{
public void eat{
System.out.println("动物会吃东西");
}
public void run{
System.out.println("动物会跑");
}
}
// 子类
public class Cat extends Animal{
// 方法重写
public void eat{
System.out.println("狗喜欢吃肉和骨头");
}
}
public class hello {
public static void main(String args[]) {
Cat cat = new Cat();
cat.eat(); // 现在子类中找eat方法,有则执行,没有则在父类中找
}
}
- super关键字
- 当子类重写父类的方法后,子类对象将无法访问父类被重写的方法,为了解决这个问题,在Java中专门提供了一个super关键字用于访问父类的成员。例如访问父类的成员变量、成员方法和构造方法。
- this和super的用法很相似,this代表本类对象的引用,super代表父类的内存空间的标识
- 当本类的成员和局部变量同名用this区分
- 当子父类中的成员变量同名用super区分父类
- 当子父类中出现成员函数一模一样的情况,会运行子类的函数。在子类覆盖方法中,继续使用被覆盖的方法可以通过super.函数名获取。
class Fu{
private int num = 4;
public int getNum(){
return num ;
}
}
class Zi extends Fu{
private int num = 5;
void show(){
System.out.println(this.num + "..." + super.getNum());
}
}
class ExtendDemo{
public static void main(String[] args){
Zi z = new Zi();
z.show();
}
}
- 子父类的构造函数
- 子父类中构造函数的特点:
在子类构造函数执行时,发现父类构造函数也运行了。那是因为子类继承了父类,获取到了父类中内容(属性),所以在使用父类内容之前,要先看父类是如何对自己的内容进行初始化的。
原因:在子类的构造函数中,第一行有一个默认的隐式语句:super();
注意:如果使用super(4);语句调用父类的其他构造函数,那么默认的父类构造函数将不会再被调用 - 当父类中没有空参数的构造函数时,子类的构造函数必须通过this或者super语句指定要访问的构造函数
- 子类构造函数中如果使用this调用了本类构造函数,那么默认的super();就没有了,因为super和this都只能定义在第一行,所以只能有一个。但是可以保证的是,子类中肯定会有其他的构造函数访问父类的构造函数。
- super语句必须要定义在子类构造函数的第一行!因为父类的初始化动作要先完成。
- 子父类中构造函数的特点:
Java——多态
-
什么是多态?
多态:相同的操作,根据对象的类型不同,表现出不同的行为,多态就是同一个接口,使用不同的实例而执行不同操作。
因为程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量倒底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。
因为在程序运行时才确定具体的类,这样,不用修改源程序代码,就可以让引用变量绑定到各种不同的类实现上,从而导致该引用调用的具体方法随之改变,即不修改程序代码就可以改变程序运行时所绑定的具体代码,让程序可以选择多个运行状态,这就是多态性。 -
实现多态的条件:
- 继承:必须要有子类继承父类的继承关系。
- 重写:子类需要对父类中的一些方法进行重写,然后调用方法时就会调用子类重写的方法而不是原本父类的方法。
- 向上转型:在多态中需要将子类的引用赋给父类对象,只有这样该引用才能够具备技能调用父类的方法和子类的方法。
在使用多态后的父类引用变量调用方法时,会调用子类重写后的方法。
-
多态的定义
父类类型 变量名=new 子类类型();
- 多态中成员的特点
- 多态成员变量:编译运行看左边(父)
Fu f=new Zi();
System.out.println(f.num);//f是Fu中的值,只能取到父中的值
2.多态成员方法:编译看左边,运行看右边(子)
Fu f1=new Zi();
System.out.println(f1.show());
//f1的门面类型是Fu,但实际类型是Zi,所以**调用的是重写后的方法**。
- 多态的转型
- 向上转型:多态本身就是向上转型过的过程
(1)使用格式:父类类型 变量名=new 子类类型();
- 向上转型:多态本身就是向上转型过的过程
Animal dog=new Dog();//向上转型
(2)适用场景:当不需要面对子类类型时,通过提高扩展性,或者使用父类的功能就能完成相应的操作。
(3)通过向上转型可以用此对象访问父类变量和子类重写的父类方法(子类没有重写父类方法的话就只能访问父类里面的方法),不能通过此对象访问子类新增的方法即子类有但父类里没有的方法
class Animal{
int num=100;
void say(){
System.out.println("It's an Animal.");
}
}
class Dog extends Animal{
int num=50;
void say(){
System.out.println("It's a Dog.");
}
void bark(){
System.out.println("汪汪汪!");
}
}
public class example{
public static void main(String[] args) {
Animal dog=new Dog();//向上转型
System.out.println(dog.num);
dog.say();
// dog.bark();
}
}
输出结果:
100
It's a Dog.
- 向下转型:一个已经向上转型的子类对象可以使用强制类型转换的格式,将父类引用类型转为子类引用各类型
(1)使用格式:子类类型 变量名=(子类类型) 已向上转型的变量名;
向下转型需要建立在向上转型的基础之上,即在进行向下转型的时候必须要有向上转型的步骤否则会报错
Animal dog=new Dog();//向上转型
Dog dog2=(Dog) dog;//向下转型
(2)适用场景:当要使用子类特有功能时。
(3)通过向下转型我们可以用此对象访问父类和子类里的变量,同样我们可以通过此对象访问父类和子类里的方法。(所以我们可以看出来向下转型它访问的范围要比向上转型访问的范围要大很多)
(4)注意:如果子类变量和父类变量重名,则访问该变量值优先访问的是子类里该变量的值,如果子类方法重写了父类方法,同理,访问该方法优先访问的是子类重写的方法。
class Animal{
int num=100;
void say(){
System.out.println("It's an Animal.");
}
}
class Dog extends Animal{
int num=50;
void say(){
System.out.println("It's a Dog.");
}
void bark(){
System.out.println("汪汪汪!");
}
}
public class example{
public static void main(String[] args) {
Animal dog=new Dog();//向上转型
Dog dog2=(Dog) dog;//向下转型
System.out.println(dog2.num);
dog2.say();
dog2.bark();
}
}
输出结果:
50
It's a Dog.
汪汪汪!