Administrator
发布于 2023-01-18 / 9 阅读
0
0

Java入门(七)——三大特性

封装

封装其实就是针对类属性的私有化(封装),即属性私有。同时向外部提供公有的set和get方法。

public class Cat {
    private String name;

    private int age;

    public void sleep() {
        System.out.println(this.name + "爱睡觉");
    }

    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;
    }
}

public class Test {
    public static void main(String[] args) {
        Cat cat = new Cat();
        System.out.println(cat.getName());

        cat.setName("年年");
        System.out.println(cat.getName());
        cat.sleep();
    }
}

输出结果

null
年年
年年爱睡觉

封装的作用

  • 提高程序的安全性,保护数据

  • 隐藏代码的实现细节

  • 统一用户的调用接口

  • 提高系统的可维护性

良好的封装,便于修改内部代码, 可进行数据完整性检测,提高可维护性和保证数据的有效性。

继承

继承的本质是对某一批类的抽象,从而实现对现实世界更好的建模。

为什么需要继承?继承的作用?

  1. 继承的本质在于抽象。类是对对象的抽象,继承是对某一批类的抽象。
  2. 为了提高代码的复用性。

继承是类和类之间的一种关系。子类继承父类,使用关键字extends来表示。extends的意思是扩展”。

public class Cat extends Animal{}

子类和父类具有“is a“的关系,上例中Cat is a Animal(猫是动物)。java中类只有单继承,子类只能继承一个父类,但父类可以有多个子类。但是接口之间可以多继承。

父类中的属性和方法可以被子类继承

每一个类默认继承Object类。

super关键字

上面我们知道了this代表当前对象,而super则代表父类对象。可以使用super调用父类的属性或方法。

public class Animal {
    private String name;
    private int age = 3;

    public void eat() {
        System.out.println("动物吃东西");
    }

    public Animal() {
        System.out.println("父类无参初始化");
    }

    public Animal(String name) {
        this.name = name;
        System.out.println("父类有参初始化");
    }

    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;
    }
}

public class Cat extends Animal {
    private int sex;

    public Cat() {
        ////默认在第一行使用super()调用父类的无参构造方法
        System.out.println("子类无参初始化");
    }

    public Cat(String name) {
        //也可以通过super(参数列表);显示调用父类有参构造器
        super(name);
        System.out.println("子类有参初始化");
    }

    public int getSex() {
        return sex;
    }

    public void setSex(int sex) {
        this.sex = sex;
    }
}

public class Test {
    public static void main(String[] args) {
        Cat cat = new Cat();
        System.out.println(cat.getAge());
        cat.setAge(2);
        System.out.println(cat.getAge());
        cat.eat();
        System.out.println("=============");
        Cat cat1 = new Cat("小猫");
        System.out.println(cat1.getName());
    }
}

输出结果

父类无参初始化
子类无参初始化
3
2
动物吃东西
=============
父类有参初始化
子类有参初始化
小猫

super注意点

  1. super调用父类构造方法,必须是构造方法中的第一个语句。
  2. super只能出现在子类的方法或者构造方法中。
  3. super this 不能够同时调用构造方法。(因为this也是在构造方法的第一个语句)

方法重写

方法重写只存在于子类和父类(包括直接父类和间接父类)之间。在同一个类中方法只能被重载,不能被重写。

静态方法可以被继承,但不能重写

  1. 父类的静态方法不能被子类重写为非静态方法 //编译出错
  2. 父类的非静态方法不能被子类重写为静态方法;//编译出错
  3. 子类可以定义与父类的静态方法同名的静态方法(但是这个不是覆盖),但是调用时只和变量声明的类型相关。
A类继承B类 A和B中都一个相同的静态方法test
B a = new A();
a.test();//调用到的是B类中的静态方法test
A a = new A();
a.test();//调用到的是A类中的静态方法test

私有方法无法访问,所以无法重写。

重写的语法

  1. 方法名必须相同
  2. 参数列表必须相同
  3. 访问控制修饰符可以被扩大,但是不能被缩小: public protected default private
  4. 抛出异常类型的范围可以被缩小,但是不能被扩大 ClassNotFoundException ---> Exception
  5. 返回类型可以相同,也可以不同,如果不同的话,子类重写后的方法返回类型必须是父类方法返回类型的子类型

方法重写是为了满足子类特定功能需要的扩展。

多态

多态概念

  • 多态是方法或对象具有多种形态,是面向对象的第三大特征
  • 多态前提是两个对象(类)存在继承关系,多态是建立在封装和继承基础之上的。

具体实现

对象的多态是多态的核心和重点

规则:

  • 一个对象的编译类型与运行类型可以不一致
  • 编译类型在定义对象时,就确定了,不能改变,而运行类型是可以变化的
  • 编译类型看定义对象时 = 号的左边运行类型看 = 号的右边

注意事项:

  1. 多态是方法的多态,属性没有多态性。
  2. 编写程序时,如果想调用运行时类型的方法,只能进行类型转换。不然通不过编译器的检查。但是如果两个没有关联的类进行强制转换,会报:ClassCastException。
  3. 多态的存在要有3个必要条件:要有继承,要有方法重写,父类引用指向子类对象

存在条件:

  1. 有继承关系
  2. 子类重写父类方法
  3. 父类引用指向子类对象,即向上转型。

注:java中的方法调用,是运行时动态和对象绑定的,不到运行的时候,是不知道到底哪个方法被调用的。

注:多态可以通过方法重写、接口、抽象类和抽象方法等方式实现。

向上转型

本质:父类的引用指向子类的对象(自动转型)

特点:

  • 编译类型看左边,运行类型看右边
  • 可以调用父类的所有成员(需遵守访问权限)
  • 不能调用子类的特有成员
  • 运行效果看子类的具体实现

语法:

父类类型 引用名 = new 子类类型();
//右侧创建一个子类对象,把它当作父类看待使用

代码示例

public class Animal {
    public void sleep(){
        System.out.println("动物睡觉");
    }
}

public class Cat extends Animal {
    @Override
    public void sleep() {
        System.out.println("猫睡觉");
    }
}

public class Dog extends Animal {
    @Override
    public void sleep() {
        System.out.println("狗睡觉");
    }
}

public class Test {
    public static void main(String[] args) {
        Animal a1 = new Cat();
        a1.sleep();
        Animal a2 = new Dog();
        a2.sleep();
    }
}

输出结果

猫睡觉
狗睡觉

向下转型

本质:一个已经向上转型的子类对象,将父类引用转为子类引用(强制转型)

特点

  • 只能强制转换父类的引用,不能强制转换父类的对象
  • 要求父类的引用必须指向的是当前目标类型的对象
  • 当向下转型后,可以调用子类类型中所有的成员

语法

子类类型 引用名 = (子类类型) 父类引用;
//用强制类型转换的格式,将父类引用类型转为子类引用类型

注:使用强制类型转换调用方法时,可能出现ClassCastException 类型转换异常。

public class Animal {
    public void sleep(){
        System.out.println("动物睡觉");
    }
}
public class Cat extends Animal {
    @Override
    public void sleep() {
        System.out.println("猫睡觉");
    }

    public void aaa(){
        System.out.println("猫抓老鼠");
    }
}
public class Dog extends Animal{
    @Override
    public void sleep() {
        System.out.println("狗睡觉");
    }

    public void bbb(){
        System.out.println("狗看家");
    }
}
public class Test {
    public static void main(String[] args) {
        Animal a = new Cat();
        a.sleep();
        Dog d = (Dog) a;
        d.sleep();
    }
}

输出结果

猫睡觉
Exception in thread "main" java.lang.ClassCastException: org.example.test.Cat cannot be cast to org.example.test.Dog
	at org.example.test.Test.main(Test.java:8)

为了避免类型转换异常的问题,我们引出 instanceof 比较操作符,用于判断对象的类型是否为XX类型或XX类型的子类型

  • 格式:对象 instanceof 类名称
  • 解释:这将会得到一个boolean值结果,也就是判断前面的对象能不能当作后面类型的实例
public class Test {
    public static void main(String[] args) {
        Animal a = new Cat();
        a.sleep();
        if (a instanceof Dog) {
            Dog d = (Dog) a;
            d.sleep();
        } else if (a instanceof Cat) {
            Cat c = (Cat) a;
            c.aaa();
        }
    }
}

输出结果

猫睡觉
猫抓老鼠

方法绑定

执行调用方法时,系统根据相关信息,能够执行内存地址中代表该方法的代码。分为静态绑定和动态绑定。

静态绑定:在编译期完成,可以提高代码执行速度。

动态绑定:通过对象调用的方法,采用动态绑定机制。这虽然让我们编程灵活,但是降低了代码的执行速度。这也是JAVAC/C++速度慢的主要因素之一。JAVA中除了final类、final方、static方法,所有方法都是JVM在运行期才进行动态绑定的。

多态:如果编译时类型和运行时类型不一致,就会造成多态。

内部类

内部类就是在一个类的内部在定义一个类,比如,A类中定义一个B类,那么B类相对A类来说就称为内部类,而A类相对B类来说就是外部类了。

外部类访问成员内部类访问方式:创建对象,再访问。

内部类分为四种:

  1. 成员内部类
  2. 静态内部类
  3. 局部内部类
  4. 匿名内部类

成员内部类

成员内部类是定义在外部类的成员位置上的内部类。

成员内部类的使用 
1.可以直接访问外部类的所有成员,包括私有的。

2.成员内部类中不能写静态属性和方法。

3.作用域和外部类的其他成员一样,为整个类体。

4.如果外部类和内部类的成员重名时,内部类访问的话,遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问。

5.外部其他类访问成员内部类有三种方式:第一种实例化外部类,然后访问;第二种将内部类作为外部类的成员实例化然后访问;第三种在外部类里面编写一个方法,可以返回内部类对象。(其实也可以理解为一种,最终都是通过实例化内部类再访问)

public class Test {
    public static void main(String[] args) {
        //第一种方式 方法调用
        Outer outer = new Outer();
        outer.sayInnerHello();
        //第二种方式 实例化内部类调用
        Outer.Inner inner = outer.new Inner();
        inner.sayHello();
        //第三种方式 返回内部类对象 再进行调用
        inner = outer.getInner();
        inner.sayHello();
    }
}

//外部类
class Outer {
    private String name = "外部类";

    public void sayHello() {
        System.out.println("outer:name=" + name);
    }

    public void sayInnerHello() {
        Inner inner = new Inner();
        inner.sayHello();
    }

    //内部类
    public class Inner {
        private String name = "内部类";

        public void sayHello() {
//            Outer.this.sayHello();
            System.out.println("inner:name=" + name);
            System.out.println("outer:name=" + Outer.this.name);
            System.out.println("======");
        }
    }

    //返回内部类
    public Inner getInner() {
        return new Inner();
    }
}

输出结果

inner:name=内部类
outer:name=外部类
======
inner:name=内部类
outer:name=外部类
======
inner:name=内部类
outer:name=外部类
======

静态内部类

静态内部类是定义在外部类的成员位置上,并且有static修饰。静态内部类能够直接被外部类给实例化,不需要使用外部类对象。

Outer.Inner inner = new Outer.Inner();

特点:

  1. 可以直接访问外部内的所有静态成员,包含私有的,但不能直接访问非静态的成员 
  2. 成员内部类中可以写静态属性和方法,也可以写非静态的。
  3. 作用域和外部类的其他成员一样,为整个类体
  4. 如果外部类和内部类的成员重名时,内部类访问的话,遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问。

局部内部类

  1. 局部内部类可以直接访问外部类的所有成员,包含私有的。
  2. 局部内部类不能添加访问修饰符,因为它的地位就是一个局部变量,局部变量是不能使用访问修饰符的,但是可以使用final修饰。
  3. 作用域仅仅在定义它的方法或代码块中。
  4. 外部类访问局部内部类,访问方式:创建对象,再访问(注意:必须在作用域内)
  5. 外部其他类不能直接访问局部内部类(因为局部内部类是一个局部变量) 
  6. 如果外部类和局部内部类的成员重名时,默认遵循就近访问原则,如果打破原则访问外部类成员,则可以使用(外部类名.this.成员)去访问。
public class Outer {
    private int y = 3;
    private void sayHello(){
        System.out.println("hi");
    }

    public void test() {
        int y = 5;

        class Inner {
            private void printOuter(){
                System.out.println(Outer.this.y);
            }

            private void print(){
                System.out.println(y);
            }

            private void hi(){
                sayHello();
            }
        }

        Inner inner = new Inner();
        inner.print();
        inner.hi();
        inner.printOuter();
    }
}

public class Test {
    public static void main(String[] args) {
        Outer outer = new Outer();
        outer.test();
    }
}

输出结果

5
hi
3

匿名内部类

【注】这是工作中最常用的一种内部类。

匿名对象

什么是匿名对象?简单来说,如果一个对象只需要使用一次,那么我们就是需要new Object().method() 就可以,而不需要生成变量引用,这就是匿名对象。匿名内部类跟匿名对象是一个道理。

匿名内部类

1. 匿名内部类需要依托于其他类或者接口来创建,如果依托的是类,那么创建出来的匿名内部类就默认是这个类的子类。如果依托的是接口,那么创建出来的匿名内部类就默认是这个接口的实现类。

2. 匿名内部类的声明必须是在使用new关键字的时候,创建时一般需要重写方法,然后调用。

3. 因为没有名字,匿名内部类没有构造器,也无法被继承。

4. 匿名内部类可以减少代码冗余

new 外部类().方法(new 接口|抽象类|父类(){
  接口方法的实现();
  或
  重写抽象类或父类的方法() ;
}).method();

接口名称 obj = new 接口名称() { 
     重写抽象方法; 
}
obj.method();

示例

public interface School {
    void info();
    void testScore();
}

public class Student {
    private String name;
    private static int age = 18;

    private int score;

    public void hi() {
        System.out.println("打招呼");
    }

    public static void go() {
        System.out.println("上学");
    }

    public Student() {

    }

    public Student(String name, int score) {
        this.name = name;
        this.score = score;
    }

    //方式一  在new中重写方法,通过变量名调用
    public void test() {
        int score = 90;//重名属性  默认就近原则
        //new接口时重写方法并声明变量
        School s = new School() {
            @Override
            public void info() {
                System.out.println(Student.this.name);
                System.out.println(Student.age);
                System.out.println(Student.this.score);
                Student.go();
                Student.this.hi();
            }

            @Override
            public void testScore() {
                System.out.println("考试得分:" + score);
            }
        };
        //调用方法
        s.info();
        s.testScore();
    }

    //链式调用  但只能调用一个方法
    public void test2() {
        int score = 60;
        //直接重写并调用
        new School() {
            @Override
            public void info() {

            }

            @Override
            public void testScore() {
                System.out.println("考试得分:" + score);
            }
        }.testScore();
    }
}

public class Test {
    public static void main(String[] args) {
        new Student("小明",80).test();
        System.out.println("=========");
        new Student().test2();
    }
}

输出结果

小明
18
80
上学
打招呼
考试得分:90
=========
考试得分:60


评论