Java面向对象的一些特性

简介: Java面向对象的一些特性

Java面向对象的一些特性

之前主要用的JS/TS,而且Rust的面向对象有些特殊。Java与JS/TS相比,在面向对象方面有许多特性。所以用一篇文章记录一下这些特性。

类的静态代码块

实例对象可以调用类的静态属性/方法。

Java中可以在类中声明静态代码块,当类加载完成后进行自动调用

public class demo3 {
    public static void main(String[] args) {
        new Demo();
        Demo.fn();
        new Demo();//只执行一次(测试静态代码块...)
    }
}
class Demo {
    static {
        System.out.println("测试静态代码块...");
    }
    static void fn(){
        System.out.println("一个静态方法");
    }
}

并且可以声明多个静态代码块:

public class demo3 {
    public static void main(String[] args) {
        new Demo();
        Demo.fn();
        new Demo();
        /*
         测试静态代码块1...
         测试静态代码块2...
         测试静态代码块3...
         一个静态方法
        */
    }
}
class Demo {
    static {
        System.out.println("测试静态代码块1...");
    }
    static {
        System.out.println("测试静态代码块2...");
    }
    static {
        System.out.println("测试静态代码块3...");
    }
    static void fn(){
        System.out.println("一个静态方法");
    }
}

类的代码块

我们也可以声明代码块来时对象生成时进行自动调用:

public class demo3 {
    public static void main(String[] args) {
        new Demo();
        /*
        测试静态代码块1...
        测试静态代码块2...
        测试静态代码块3...
        1
        2
        3
        */
    }
}
class Demo {
    static {
        System.out.println("测试静态代码块1...");
    }static {
        System.out.println("测试静态代码块2...");
    }static {
        System.out.println("测试静态代码块3...");
    }
    {
        System.out.println("1");
        System.out.println("2");
    }
    {
        System.out.println("3");
    }
}

package与import

一般情况下声明类使用全名(包名+类名)

new java.util.Date();

可import导入(package后class前):

package oop;
import java.util.Date;
//import java.util.*;导入java.util下的所有类

public class OopPackage {
    public static void main(String[] args) {
        new Date();
    }
}

java.lang下的可省略,java虚拟机会自动添加。如:

String s = "ssss";
java.lang.String s1 = "ssss";

如果在使用时用到了不同包中的同名类,还是要加上包名

package oop;

import java.util.*;
import java.sql.Date;

public class OopPackage {
    public static void main(String[] args) {
        java.util.Date d = new java.util.Date();
        java.sql.Date dd = new java.sql.Date(2022,12,31);
    }
}

实例对象

java中声明构造方法为方法名(){}(没有constructor关键字)

构造方法中的代码会在new一个实例对象时进行执行,如果类中没有声明构造方法,会自动声明一个空的构造方法。new Lang()中的()就是在调用构造方法。

public class OopPackage {
    public static void main(String[] args) {
        Lang lang = new Lang();
        /*
        静态代码块
        代码块
        构造方法
        */
    }
}
class Lang{
    Lang(){
        System.out.println("构造方法");
    }
    {
        System.out.println("代码块");
    }
    static {
        System.out.println("静态代码块");
    }
}

我们可以看到构造方法在类的代码块后执行(另外,类的代码块在静态代码块后执行)

当父类的构造方法有参数时,子类需要在构造方法中调用super(参数)。而父类的构造方法为默认(没声明,JVM自动声明)或者无参,则不需要调用super(),JVM会自动调用。

public class OopPackage {
    public static void main(String[] args) {
        Lang lang = new Lang("Chinese");
        System.out.println(lang.name);//Chinese
        CLang clang = new CLang("rust");
        System.out.println(clang.name);//rust
    }
}
class Lang{
    String name;
    Lang(String name){
        this.name = name;
    }
}
class CLang extends Lang{
    CLang(String name){
        super(name);
    }
}

在使用new关键字时,会先开辟内存空间实例化一个对象,再调用构造方法对属性进行初始化,而调用super(参数)则是为了初始化父类属性。

在前面了解到类中的代码块会在构造实例对象时会调用代码块,所以代码块会先执行,然后执行构造方法的代码。

需要注意的是:多次声明实例对象,一个类中的静态代码块只会执行一次。而继承的代码块会执行多次。另外,由于会调用super(),也就是父类的构造方法,所以也会调用多次父类中构造方法的代码。

public class OopPackage {
    public static void main(String[] args) {
        Cpp c1 = new Cpp("s");
        Cpp c2 = new Cpp("ss");
        Cpp c3 = new Cpp("sss");
//        Lang static
//        CLang static
//        Cpp static
//        Lang 代码块
//        Lang 构造方法s
//        CLang 代码块
//        CLang 构造方法s
//        Cpp 代码块
//        Cpp 构造方法s
//
//        Lang 代码块
//        Lang 构造方法ss
//        CLang 代码块
//        CLang 构造方法ss
//        Cpp 代码块
//        Cpp 构造方法ss
//
//        Lang 代码块
//        Lang 构造方法sss
//        CLang 代码块
//        CLang 构造方法sss
//        Cpp 代码块
//        Cpp 构造方法sss
    }
}
class Lang{
    String name;
    Lang(String name){
        this.name = name;
        System.out.println("Lang 构造方法"+this.name);
    }
    {
        System.out.println("Lang 代码块");
    }
    static {
        System.out.println("Lang static");
    }
}
class CLang extends Lang{
    CLang(String name){
        super(name);
        System.out.println("CLang 构造方法"+this.name);
    }
    {
        System.out.println("CLang 代码块");
    }
    static {
        System.out.println("CLang static");
    }
}

class Cpp extends CLang{
    Cpp(String name){
        super(name);
        System.out.println("Cpp 构造方法"+this.name+"\n");
    }
    {
        System.out.println("Cpp 代码块");
    }
    static {
        System.out.println("Cpp static");
    }
}

多态

多态是对对象在不同场景下进行约束,一个对象可以使用的功能取决于它的类型。

public class OopPackage {
    public static void main(String[] args) {
        Lang l1 = new CLang();
//        l1.fn();// 报错
        CLang l2 = new CLang();
        l2.fn();
    }
}
class Lang{
}
class CLang extends Lang{
    void fn(){
        System.out.println("this is fn");
    }
}

class GoLang extends Lang{
    void oop(){
        System.out.println("this is oop");
    }
}

方法重载

一个类中,同名的方法/属性只能声明一次。同名方法指的是:方法名相同,参数列表相同,与返回值类型无关。

我们可以在一个类中声明方法名相同,但参数列表不同的方法。它们会被认为是不同是方法,这样的操作叫方法重载(注意,参数列表的类型一致,就算形参名不同也被认为是同一个方法:22-24行。需要参数个数、类型、顺序不同)。

public class Oop_14 {
    public static void main(String[] args) {
        User user = new User();
        user.login("kevin","kevin123");
        user.login("WXkevin");
        user.login(1333);
//        用户名密码登录
//        微信登录
//        手机号登录
    }
}
class User{
    void login(String username,String password){
        System.out.println("用户名密码登录");
    }
    void login(int tel){
        System.out.println("手机号登录");
    }
    void login(String wx){
        System.out.println("微信登录");
    }
//    void login(String zfb){
//        System.out.println("支付宝登录");
//    } //被认为与微信登录为同一个方法
}

当然,构造方法也能方法重载:

public class Oop_14 {
    public static void main(String[] args) {
        User user = new User("kevin");
        User user1 = new User(1333);
        //    用户名是:kevin
        //    电话号是:1333
    }
}
class User{
    User(String username){
        System.out.println("用户名是:"+username);
    }
    User(int tel){
        System.out.println("电话号是:"+tel);
    }
}

构造方法与默认参数:

public class Oop_15 {
    public static void main(String[] args) {
        Users users = new Users();
        Users users1 = new Users("Deno");
        Users users2 = new Users("Bun",1222);
    }
//    kevin,133
//    Deno,133
//    Bun,1222
}

class Users{
    Users(){
        this("kevin");
    }
    Users(String name){
        this(name,133);
    }
    Users(String name,int tel){
        System.out.println(name+","+tel);
    }
}

方法重载与类型转换

我们知道一个java文件就是一个类,我们直接在类文件中重载方法来测试数值类型传参的精度转换:

public class Oop_142 {
    public static void main(String[] args) {
        byte b = 10;
        demo(b);//byte
    }
    static void demo(byte i){
        System.out.println("byte");
    }
    static void demo(short i){
        System.out.println("short");
    }
    static void demo(int i){
        System.out.println("int");
    }
    static void demo(long i){
        System.out.println("long");
    }
    static void demo(char i){
        System.out.println("char");
    }
}

删去byte

public class Oop_142 {
    public static void main(String[] args) {
        byte b = 10;
        demo(b);//short
    }
    static void demo(short i){
        System.out.println("short");
    }
    static void demo(int i){
        System.out.println("int");
    }
    static void demo(long i){
        System.out.println("long");
    }
    static void demo(char i){
        System.out.println("char");
    }
}

删去short

public class Oop_142 {
    public static void main(String[] args) {
        byte b = 10;
        demo(b);//int
    }
    static void demo(int i){
        System.out.println("int");
    }
    static void demo(long i){
        System.out.println("long");
    }
    static void demo(char i){
        System.out.println("char");
    }
}

删去int

public class Oop_142 {
    public static void main(String[] args) {
        byte b = 10;
        demo(b);//long
    }
    static void demo(long i){
        System.out.println("long");
    }
    static void demo(char i){
        System.out.println("char");
    }
}

删去long

public class Oop_142 {
    public static void main(String[] args) {
        byte b = 10;
        demo(b);//报错:因为byte不能转为char,char没有负数,而byte有
    }

    static void demo(char i){
        System.out.println("char");
    }
}

方法重载与多态

public class Oop_143 {
    public static void main(String[] args) {
        AA bb = new BB();
        test(bb);//aaa
        BB bb1 = new BB();
        test(bb1);//bbb
    }
    static void test(AA a){
        System.out.println("aaa");
    }
    static void test(BB b){
        System.out.println("bbb");
    }
}
class AA{
}
class BB extends AA{
}

类似JS的原型链,在传参时,对于对象这种引用类型。如果类型不一致,则会去找它的父类:

public class Oop_143 {
    public static void main(String[] args) {
        BB bb = new BB();
        test(bb);//aaa
    }
    static void test(AA a){
        System.out.println("aaa");
    }
}
class AA{
}
class BB extends AA{
}

方法重写

子类声明父类的相同方法(返回类型、参数类型、方法名一致)会重写方法。建议写上@Override,这个注释会告诉编译器检查这个方法,保证父类要包含一个被该方法重写的方法,否则就会编译出错。这样可以帮助程序员避免一些低级错误。

class Parent{
    void demo(){
        System.out.println("父类方法");
    }
}
class Child extends Parent{
    @Override
    void demo() {
        super.demo();
    }
}

我们之前了解到:对象使用什么方法取决于它的类型,我们看看下面这个例子:

public class Oop_15_2 {
    public static void main(String[] args) {
        Ccc ddd = new Ddd();
        System.out.println(ddd.sum());//40
    }
}
class Ccc{
    int i = 10;
    int sum(){
       return i+=10;
    }
}
class Ddd extends Ccc{
    int i = 20;

    @Override
    int sum() {
        return i+=20;
    }
}

可以看到,虽然ddd的类型是Ccc。之前说对象能使用什么方法/属性,取决于类型。但是具体怎么使用,得看具体的对象。比如这个实例对象的类中重写了父类的方法,所以用的的Ddd类中的sum()方法。

需要注意的是:下面例子Ccc类中方法中的ithis.i,所以在调用时使用的id 值为10而不是20。

public class Oop_15_2 {
    public static void main(String[] args) {
        Ccc ddd = new Ddd();
        System.out.println(ddd.sum());//20
    }
}
class Ccc{
    int i = 10;
    int sum(){
       return i+=10;
    }
}
class Ddd extends Ccc{
    int i = 20;
}

在看一个例子:

public class Oop_15_2 {
    public static void main(String[] args) {
        Ccc ddd = new Ddd();
        System.out.println(ddd.sum());//30
    }
}
class Ccc{
    int i = 10;
    int sum(){
       return getI()+10;
    }
    int getI() {
        return i;
    }
}
class Ddd extends Ccc{
    int i = 20;
    @Override
    int getI() {
        return i;
    }
}

在上面这个例子中,sum只存在于父类,而返回值是getI()+10。在子类中我们重写了getI(),子类中的getI()返回值为i=20,所以这里sum()的返回值为20+10

递归

求奇数和与阶乘:

package oop;

public class Oop_16 {
    public static void main(String[] args) {
        System.out.println(sum(9));//25
        System.out.println(jiecheng(5));//120
    }
    static int sum(int n){
        n = (n+1)%2==0 ? n: --n;
        return n < 0 ? 0: n+sum(n-2);
    }
    static int jiecheng(int n){
        return n == 1 ? 1 : n * jiecheng(n-1);
    }
}

访问权限

下面关键字表示的权限由小到大:

1.private:只有该类中可以使用

2.(default):JVM虚拟机默认添加,包权限(同个package中可以直接使用而不用import)。

3.protected:子类可以使用

4.public:公共的

同一个类 同一个包 不同包的子类 不同包的非子类
Private
Default
Protected
Public

private

同类中使用

声明私有属性/方法,只有该类中可以使用:

public class Oop_17 {
    public static void main(String[] args) {
        DemoClass demo = new DemoClass();
        demo.test();//kevin
//        demo.name;//报错,因为name是私有属性
    }
}
class DemoClass{
    private String name = "kevin";
    void test(){
        System.out.println(name);
    }
}

default

同类,同包中使用

如果没有修饰词声明访问权限,JVM会自动添加default修饰词。这个修饰词表示的访问权限为包访问权限,只能在同一个包中访问。在同一个包中可以任意访问。

protected

同类,同包,子类中使用

我们先看一个例子:

我们在java.lang.Object中找到clone()方法,这个方法的访问权限为protected,我们声明的类都继承自Object类,那我们声明一个类,能否使用clone()方法呢?

package oop;

public class Father {
    public static void main(String[] args) {
        Person person = new Person();
//        person.clone();//'clone()' 在 'java.lang.Object' 中具有 protected 访问权限
    }
}
class Person{}

我们得到了报错:clone()java.lang.Object中具有 protected 访问权限

这是因为Father类的父类虽然与Person类的父类都是Object,但它们的super不是指向的同一个。而在这里是由Father去访问clone,但是访问的是person的clone,所以无法访问。

我们可以这样来调用clone()方法:

class Person{
    void test() throws Exception{
        clone();
    }
}

关于protected的一些结论:

  • 同一包内,普通类或子类都可以访问父类的protected方法;
  • 不同包内,在子类中创建子类对象可以访问父类的protected方法;
  • 不同包内,在子类中创建父类对象不能访问父类的protected方法;
  • 不同包内,在子类中创建另一个子类的对象不能访问公共父类的protected方法;
  • 父类protected方法加上static修饰符,子类可以直接访问父类的protected方法。

同一包内,普通类或子类都可以访问父类的protected方法;

package oop;

public class Father {
    protected void test(){
        System.out.println("father protected");
    }
}

class Person1 extends Father {
    void demo(){
        Father father = new Father();
        father.test();
        test();
    }
}
class Person2{
    void demo(){
        Father father = new Father();
        father.test();
    }
}

不同包下,在子类中只要是通过引用父类(多态),不可以访问其protected方法;

但是通过引用子类,可以访问其protected方法;

//包oop
package oop;

public class Father {
    protected void test(){
        System.out.println("father protected");
    }
}



//包oop_1
package oop_1;

import oop.Father;

public class Son extends Father {
    public static void main(String[] args) {
        Father son1 = new Son();
//      son1.test();//'test()' 在 'oop.Father' 中具有 protected 访问权限

        Son son = new Son();
        son.test();//father protected
    }
}

父类protected方法加上static修饰符,子类可以直接访问父类的protected方法。

//包oop
package oop;

public class Father {
    static protected void test(){
        System.out.println("father protected");
    }
}

//包oop_1
package oop_1;

import oop.Father;

public class Son extends Father {
    public static void main(String[] args) {
        test();//子类中可以直接访问
    }
}

而不同包非子类不能访问:

package oop;

public class Father {
    static protected void test(){
        System.out.println("father protected");
    }
}


package oop_1;

import oop.Father;

public class Son {
    public static void main(String[] args) {
//        Father.test();//'test()' 在 'oop.Father' 中具有 protected 访问权限
    }
}

public

公共的

public修饰词的访问权限最大,表示随意访问。

下例文件位置与oop.Oop_16不在同一个包下

import oop.Oop_16;

public class Main {
    public static void main(String[] args) {
        Oop_16 oop16 = new Oop_16();
    }
}

我们在一个Java源文件中可以看到有个公共类,类名与Java文件名一致。在Java文件中只能有一个公共类,并且类名为文件名。

另外,类中有个主函数。它的修饰词为:public static。使用public是因为该方法由JVM虚拟机调用,而public作为最大的访问权限,可以任意访问。而使用static是因为在调用时不需要实例对象,就算能够构建实例对象,JVM也不知道该如何传参。所以主函数main方法是由文件类来调用,故使用static关键字。

public class Oop_17 {
    public static void main(String[] args) {

    }
}
//public class NNN{}//报错:public class NNN应该在NNN.java中声明

同时,一个文件中也可以有多个main方法(在不同类中声明)

内部类

外部类:直接在源码中声明的类,如下例的Oop_18OuterClass。内部类:在外部类中声明的类,可以多层嵌套内部类。

Java中不允许外部类使用private,protected关键字。内部类作为外部类的一个属性使用,内部类可以使用private,protected关键字。

package oop;

public class Oop_18 {
    public static void main(String[] args) {
        OuterClass outer = new OuterClass();
        OuterClass.InnerClass inner = outer.new InnerClass();
        OuterClass.InnerClass.InInner inInner = inner.new InInner();
    }
}

class OuterClass{
    public class InnerClass{
        protected class InInner{

        }
    }
}

因为内部类被当做外部类的属性使用,所以我们使用的时候也应该构建实例对象(不使用static)。

当然,由于内部类被当做属性看待,我们也可以使用static关键字来让外部类直接访问。

package oop;

public class Oop_18 {
    public static void main(String[] args) {
        OuterClass.InnerClass.test();//Inner test
    }
}

class OuterClass{
     static public class InnerClass{
        static void test(){
            System.out.println("Inner test");
        }
    }
}

单例模式

在不显式声明构造方法时JVM会自动声明一个由public修饰的无参构造方法。同时,构造方法也是可以使用privateprotected修饰的。我们有时会使用private修饰构造方法,这样做的原因有两个:

  • 类的构造过程很复杂
  • 为了减少类构造对象时的资源消耗

我们先来尝试写一些代码:

package oop;

public class Oop_19 {
    public static void main(String[] args) {
        User_19 user19 = User_19.getInstance();
    }
}
class User_19{
    private User_19(){}
    static User_19 getInstance(){
        return new User_19();
    }
}

然而这样并没有解决减少创建对象时的资源消耗,我们多次调用User_19.getInstance()new User_19()的效果是相同的,我们希望不管调用多少次User_19.getInstance()都是指向的同一个对象,接下来我们来修改下代码:

package oop;

public class Oop_19 {
    public static void main(String[] args) {
        User_19 user19 = User_19.getInstance();
        User_19 user19_1 = User_19.getInstance();
        User_19 user19_2 = User_19.getInstance();
        System.out.println(user19==user19_2);//true
    }
}
class User_19{
    static User_19 user19 = null;
    private User_19(){}
    static User_19 getInstance(){
        if (user19 == null){
            user19 = new User_19();
        }
        return user19;
    }
}

这时候不管声明多少次,都会指向同一个实例对象。这个设计模式叫做单例模式。

final

修饰变量/属性

Java中使用final来声明常量。

我们可以使用final修饰变量,声明时可以不初始化值。但是无法进行调用:

final String s;
//System.out.println(s);//变量 's' 可能尚未初始化

使用final声明的变量无法修改(常量)

我们也可以在类中声明常量属性,但是需要进行初始化:

public class Oop_21 {
    public static void main(String[] args) {
        final String s = "kevin";
//        s = "ss";//无法将值赋给 final 变量 's'
    }
}
class OopFinal{
    final int tel = 1333;
    void change(){
//        this.tel = 1222;//无法将值赋给 final 变量 'tel'
    }
}

我们也可以通过构造方法来对常量属性进行初始化,这样会更加灵活:

public class Oop_21 {
    public static void main(String[] args) {
        OopFinal oopFinal = new OopFinal(1349999);
//        oopFinal.tel = 2222;//无法将值赋给 final 变量 'tel'
        System.out.println(oopFinal.tel);//1349999
    }
}
class OopFinal{
    final int tel;
    OopFinal(int tel){
        this.tel = tel;
    }
}

修饰方法

我们也可以使用final来声明一个方法,这表示不允许被子类重写:

class OopFinal{
    final void test(){}
}
class OopFinalFn extends OopFinal{
//    void test(){}//'test()' 无法重写 'oop.OopFinal' 中的 'test()';重写的方法为 final
}

修饰类

可以使用final来修饰类,被final修饰的类没有子类:

final class OopFinal{
    final void test(){}
}
//class OopFinalFn extends OopFinal{}//无法从final 'oop.OopFinal' 继承

修饰方法参数

final也可以修饰方法的参数,被修饰的参数无法被修改:

class OopFinal{
    void test(final int tel){
//        tel = 123;//无法将值赋给 final 变量 'tel'
    }
}

无法修饰构造方法

final无法修饰构造方法,我们使用final修饰方法是为了避免重写。而构造方法不会重名,因为构造方法的方法名与类名一致。

class OopFinal{
//    final OopFinal(){}//此处不允许使用修饰符 'final'
}

抽象

在不清楚类或者方法的具体实现时,我们可以使用抽象来编写代码(抽象类,抽象方法)。这是因为我们可能会遇到先有一个对象,然后通过这个对象来抽象为一个类。

我们通常在分析问题时,这个过程是具体到抽象(对象=>类)。而编写代码时是抽象到具体(类=>对象)

抽象方法和抽象类

使用abstract来修饰方法,表明这是个抽象方法(没有具体实现)。语法为:abstract 返回类型 方法名(参数);

而声明了抽象方法,那么也必须要对这个使用abstract修饰。语法为abstract class 类名{}

abstract class Abstract_21{
    abstract void test(String name);
}

我们不能使用abstract修饰属性,因为属性不需要抽象,直接不声明就行了

//abstract String s;//此处不允许使用修饰符 'abstract'

此外抽象类中并不是全都是抽象方法

abstract class Abstract_21{
    void demo(){}
    abstract void test(String name);
}

并且可以不声明抽象方法

abstract class Abstract_21{
    void demo(){}
}

另外,抽象类不能被实例化。必须被子类继承并且实现抽象方法:

public class Oop_Abstact_21 {
    public static void main(String[] args) {
//        Abstract_21 abstract21 = new Abstract_21();//'Abstract_21' 为 abstract;无法实例化
    }
}
abstract class Abstract_21{
    abstract void test(int tel);
}
class Abstract_21_ extends Abstract_21{
    @Override
    void test(int tel) {
        System.out.println("电话号码为:"+tel);
    }
}

虽然抽象类不能被实例化,但是我们可以在子类中使用super关键字来调用抽象类的属性和方法:

public class Oop_Abstact_21 {
    public static void main(String[] args) {
        Abstract_21_ abstract_21_ = new Abstract_21_();
        abstract_21_.test(1333);
//        demo
//        kevin
//        电话号码为:1333
    }
}
abstract class Abstract_21{
    String name = "kevin";
    void demo(){
        System.out.println("demo");
    }
    abstract void test(int tel);
}
class Abstract_21_ extends Abstract_21{
    @Override
    void test(int tel) {
        super.demo();
        System.out.println(super.name);
        System.out.println("电话号码为:"+tel);
    }
}

final关键字和abstract无法同时使用。对于类,final禁止声明子类,但是抽象类需要子类才能使用。对于方法,final禁止重写,但抽象方法需要在子类中重写。

接口

接口也是一种抽象,是对类的一种约束。基本语法:interface 接口名{约束的属性,约束的行为}

interface DemoInterface{
    int tel = 1333;
    void test();
}

对于接口,我们应该知道:

  • 接口的属性必须是固定值,并且不能被修改。而行为默认是抽象的。
  • 接口的属性和方法的访问权限都是公共的,并且属性都是静态的,因为它某个对象无关。
  • 接口与类是两个层面的,接口是类的约束。但接口可以继承其他接口。
  • 类需要遵循接口这个约束,在Java中叫做实现,我们使用implements来实现这个接口,并且可以实现多个接口。
public class Oop_Interface_22 {
    public static void main(String[] args) {
        DemoInterface demoInterface = new DDee();
        demoInterface.test();//实现
        DDee ddee = new DDee();
        ddee.test();//实现
    }
}
interface DemoInterface{
    int tel = 1333;
    void test();
}
class DDee implements DemoInterface{
    @Override
    public void test() {
        System.out.println("实现");
    }
}

再来看一个例子:

public class Oop_Interface_22 {
    public static void main(String[] args) {
        Power power = new Power();
        Light light1 = new Light();
        Light light2 = new Light();
        power.usb1 = light1;
        power.usb2 = light2;
        power.supply();
//        提供电力
//        小电灯亮了
//        小电灯亮了
    }
}
interface USBInterface{}
interface USBSupply extends USBInterface{
     void supply();
}
interface USBReceive extends USBInterface{
    void receive();
}
class Power implements USBSupply{
    USBReceive usb1;
    USBReceive usb2;
    @Override
    public void supply() {
        System.out.println("提供电力");
        usb1.receive();
        usb2.receive();
    }
}
class Light implements USBReceive{
    @Override
    public void receive() {
        System.out.println("小电灯亮了");
    }
}

枚举

枚举是一种特殊的类,其中包含了一组特殊的对象,这些对象不能被改变。一般使用全大写的标识符。

枚举使用enum关键字声明

package oop;

public class Oop_enum_23 {
    public static void main(String[] args) {
        System.out.println(City.BEIJING);//BEIJING
        System.out.println(City.SHANGHAI);//SHANGHAI
    }
}
enum City{
    BEIJING,SHANGHAI;
}

枚举是特殊的类,当然也可以声明构造方法。枚举类会将对象放在最前面,与后面的语法使用分号隔开;

因为枚举中的对象是枚举的实例对象,所以需要传参。

package oop;

public class Oop_enum_23 {
    public static void main(String[] args) {
        System.out.println(City.BEIJING.name);//北京
        System.out.println(City.BEIJING.code);//1
        System.out.println(City.SHANGHAI.name);//上海
        System.out.println(City.SHANGHAI.code);//2
    }
}
enum City{
    BEIJING("北京",1),SHANGHAI("上海",2);
    City(final String name,final int code){
        this.name = name;
        this.code = code;
    }
    final String name;
    final int code;
}

枚举类不能创建对象,它的对象是在其内部创建的。

枚举类是特殊的类,我们尝试实现一个类似的类:

public class Oop_enum_23 {
    public static void main(String[] args) {
        System.out.println(MyCity.BEIJING.name);//北京
        System.out.println(MyCity.BEIJING.code);//1
        System.out.println(MyCity.SHANGHAI.name);//上海
        System.out.println(MyCity.SHANGHAI.code);//2
    }
}
class MyCity{
    final String name;
    final int code;
    private MyCity(final String name,final int code){
        this.name = name;
        this.code = code;
    }
    public static final MyCity BEIJING = new MyCity("北京",1);
    public static final MyCity SHANGHAI = new MyCity("上海",2);
}

匿名类

在很多时候,类名是什么并不重要,我们需要的是类中的方法或者功能,这时候我们就可以使用匿名类。

而匿名类就是没有名称的类。

public class Oop_24 {
    public static void main(String[] args) {
        One one = new One();
        one.sayHello(new Student() {
            @Override
            String name() {
                return "kevin";
            }
        });//hello~kevin
        one.sayHello(new Student() {
            @Override
            String name() {
                return "qian";
            }
        });//hello~qian
    }
}
abstract class Student{
    abstract String name();
}
class One{
    void sayHello(Student student){
        System.out.println("hello~"+student.name());
    }
}

我们也可以使用接口来约束:

public class Oop_24 {
    public static void main(String[] args) {
        new Fly(){
            @Override
            public void howtoflay() {
                System.out.println("人类坐飞机");
            }
        }.howtoflay();//人类坐飞机
        new Fly(){
            @Override
            public void howtoflay() {
                System.out.println("乌鸦也坐飞机");
            }
        }.howtoflay();//乌鸦也坐飞机
    }
}
interface Fly{
    void howtoflay();
}

Bean规范

通常我们封装类的目的有两个:

  • 编写逻辑
  • 建立数据模型

而通常建立数据模型,我们会遵循Bean规范。创建的类被称为Bean类。

对于Bean规范:

  • 类必须含有无参,公共的构造方法
  • 属性必须私有化,然后提供公共的set、get方法(访问器)

下面是实现Bean规范的一个简单例子:

public class Oop_25 {
    public static void main(String[] args) {
        TheUser theUser = new TheUser();
        theUser.setAccount("kevin");
        theUser.setPassword("kevin");
        login(theUser);//登录成功
    }
    static void login(TheUser user){
        if (user.getAccount().equals("kevin")&&user.getPassword().equals("kevin")){
            System.out.println("登录成功");
        }else {
            System.out.println("账号或密码错误,登录失败");
        }
    }
}
class TheUser{
    private String account;
    private String password;

    public String getAccount() {
        return account;
    }

    public void setAccount(String account) {
        this.account = account;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}

在上面的例子中,我们将login方法放到了公共类中而不是TheUser类。这是因为login并不是用户提供,用户只是会使用这个方法。这样的逻辑更加合理。TheUser类就是一个Bean类,用于建立数据模型,其中并不会有或者只有很少的逻辑代码。

作用域

如果属性和(局部)变量名相同,访问时不加this关键字,那么优先访问变量(这时候JVM不会自动添加this关键字)。

public class Oop_26 {
    public static void main(String[] args) {
        SomeBody someBody = new SomeBody();
        someBody.test();//demo
    }
}
class Person26{
    static String name = "person";
}
class SomeBody extends Person26{
    static String name = "kevin";
    void test(){
        String name = "demo";
        System.out.println(name);
    }
}

我们可以使用super来获取父类的静态属性,因为创建实例对象的前提是有这个类。

public class Oop_26 {
    public static void main(String[] args) {
        SomeBody someBody = new SomeBody();
        someBody.test();//person
    }
}
class Person26{
    static String name = "person";
}
class SomeBody extends Person26{
    static String name = "kevin";
    void test(){
        System.out.println(super.name);
    }
}

SomeBody中的test方法为静态方法时,不能使用super。因为并不知道它是否有实例化对象。在Java中的继承是对象的继承。在这里只能使用类来调用父类的name:

public class Oop_26 {
    public static void main(String[] args) {
        SomeBody someBody = new SomeBody();
        someBody.test();//person
    }
}
class Person26{
    static String name = "person";
}
class SomeBody extends Person26{
    static String name = "kevin";
    static void test(){
//        System.out.println(super.name);//无法从 static 上下文引用 'oop.SomeBody.super'
        System.out.println(Person26.name);
    }
}
相关文章
|
30天前
|
存储 安全 Java
Java Map新玩法:探索HashMap和TreeMap的高级特性,让你的代码更强大!
【10月更文挑战第17天】Java Map新玩法:探索HashMap和TreeMap的高级特性,让你的代码更强大!
57 2
|
1月前
|
存储 Java
深入探讨了Java集合框架中的HashSet和TreeSet,解析了两者在元素存储上的无序与有序特性。
【10月更文挑战第16天】本文深入探讨了Java集合框架中的HashSet和TreeSet,解析了两者在元素存储上的无序与有序特性。HashSet基于哈希表实现,添加元素时根据哈希值分布,遍历时顺序不可预测;而TreeSet利用红黑树结构,按自然顺序或自定义顺序存储元素,确保遍历时有序输出。文章还提供了示例代码,帮助读者更好地理解这两种集合类型的使用场景和内部机制。
38 3
|
1月前
|
存储 Java 数据处理
Java Set接口凭借其独特的“不重复”特性,在集合框架中占据重要地位
【10月更文挑战第16天】Java Set接口凭借其独特的“不重复”特性,在集合框架中占据重要地位。本文通过快速去重和高效查找两个案例,展示了Set如何简化数据处理流程,提升代码效率。使用HashSet可轻松实现数据去重,而contains方法则提供了快速查找的功能,彰显了Set在处理大量数据时的优势。
33 2
|
1月前
|
存储 算法 Java
Java Set因其“无重复”特性在集合框架中独树一帜
【10月更文挑战第14天】Java Set因其“无重复”特性在集合框架中独树一帜。本文深入解析Set接口及其主要实现类(如HashSet、TreeSet)如何通过特定的数据结构(哈希表、红黑树)确保元素唯一性,并提供最佳实践建议,包括选择合适的Set实现类和正确实现自定义对象的`hashCode()`与`equals()`方法。
31 3
|
1月前
|
安全 Java API
Java 17新特性让你的代码起飞!
【10月更文挑战第4天】自Java 8发布以来,Java语言经历了多次重大更新,每一次都引入了令人兴奋的新特性,极大地提升了开发效率和代码质量。本文将带你从Java 8一路走到Java 17,探索那些能让你的代码起飞的关键特性。
80 1
|
14天前
|
分布式计算 Java API
Java 8引入了流处理和函数式编程两大新特性
Java 8引入了流处理和函数式编程两大新特性。流处理提供了一种声明式的数据处理方式,使代码更简洁易读;函数式编程通过Lambda表达式和函数式接口,简化了代码书写,提高了灵活性。此外,Java 8还引入了Optional类、新的日期时间API等,进一步增强了编程能力。这些新特性使开发者能够编写更高效、更清晰的代码。
27 4
|
22天前
|
Java 关系型数据库 数据库
面向对象设计原则在Java中的实现与案例分析
【10月更文挑战第25天】本文通过Java语言的具体实现和案例分析,详细介绍了面向对象设计的五大核心原则:单一职责原则、开闭原则、里氏替换原则、接口隔离原则和依赖倒置原则。这些原则帮助开发者构建更加灵活、可维护和可扩展的系统,不仅适用于Java,也适用于其他面向对象编程语言。
14 2
|
28天前
|
存储 Java API
优雅地使用Java Map,通过掌握其高级特性和技巧,让代码更简洁。
【10月更文挑战第19天】本文介绍了如何优雅地使用Java Map,通过掌握其高级特性和技巧,让代码更简洁。内容包括Map的初始化、使用Stream API处理Map、利用merge方法、使用ComputeIfAbsent和ComputeIfPresent,以及Map的默认方法。这些技巧不仅提高了代码的可读性和维护性,还提升了开发效率。
54 3
|
28天前
|
存储 安全 Java
Java Map新玩法:深入探讨HashMap和TreeMap的高级特性
【10月更文挑战第19天】Java Map新玩法:深入探讨HashMap和TreeMap的高级特性,包括初始容量与加载因子的优化、高效的遍历方法、线程安全性处理以及TreeMap的自然排序、自定义排序、范围查询等功能,助你提升代码性能与灵活性。
24 2
|
1月前
|
Java 开发者
在Java的集合世界里,Set以其独特的特性脱颖而出,它通过“哈希魔法”和“红黑树防御”两大绝技
【10月更文挑战第13天】在Java的集合世界里,Set以其独特的特性脱颖而出。它通过“哈希魔法”和“红黑树防御”两大绝技,有效抵御重复元素的侵扰,确保集合的纯洁性和有序性。无论是“人海战术”还是“偷梁换柱”,Set都能从容应对,成为开发者手中不可或缺的利器。
31 6