Java基础重构-面向对象

Java 是面向对象的程序设计语言,类是面向对象的重要内容,可以把了当成一种自定义类型。可以使用类来定义变量,这种类型的变量统称为引用变量。

static 是一个特殊的关键字,它可用于修饰方法,成员变量等成员。static 修饰的成员表明他属于这个类本省,而不属于该类的单个实例,因为通常把 static 修饰的成员变量和 方法 也成为类变量,类方法。不使用 static 修饰的普通方法,成员变量则属于该类的单个实例,而不属于该类。因为通常把不使用 static修饰的成员变量和方法也成为实例变量,实例方法。

对象的this引用是什么?

Java 提供了一个this 关键字,this关键字总是指向调用该方法的对象,更具 this 出现位置的不同,this作为对象的默认引用有两种情形。

  1. 构造器中引用该构造器正在初始化的对象
  2. 在方法中引用调用该方法的对象。

this关键字最大的作用就是让类中一个方法,访问该类里的另一个方法或实例变量。

this可以代表任何对象,当this出现在某个方法体重是,它所代表的对象是不确定的,但它的类型是确定的,他所代表的对象 只能是当前类;只有当这个方法被调用时,它所代表的对象才被确定下来,谁在调用这个方法,this就代表谁。

大部分时候,一个方法访问该类中定义的其他方法,成员变量时加不加this前缀的效果是完全一样的。但是对于static 修饰的方法而言,则可以使用类来直接调用该方法,如果在 static 修饰的方法中使用 this关键字,则这个关键字就无法指向合适的对象。所以,satatic 修饰的方法不能使用 this引用,所以static 修饰的方法不能访问不使用 static 修饰的普通成员,因此 Java 语法规定: 静态成员不能直接访问非静态成员。

也可以将 this 作为返回值,如果某个方法把 this 作为返回值,则可以多次连续调用同一个方法,从而使得代码更加简洁。但是,这种把 this作为返回值的方法可能造成实际意义上的模糊。

public class MyClass {
    private static int age;

    public static void main(String[] args) {
        new MyClass().vo().vo().vo();
    }

    private MyClass vo() {
        ++age;
        System.out.println(age);
        return this;
    }

}

为什么static成员不能直接访问非静态成员?

static 是一个特殊的关键字,它可用于修饰方法,成员变量等成员。static 修饰的成员表明他属于这个类本身,而不属于该类的单个实例。而我们非static 修饰的变量它属于的是实例的变量,所以stati成员不能直接

访问静态成员


为什么同一类里,静态和非静态方法可以相互调用?

因为Java里的方法不能独立存在,他必须属于一个类或一个对象,因此方法不能像函数那样被独立执行,执行方法时必须使用类或对象来作为调用者,同一个类的一个方法调用另外一个方法时,如果被调方法时普通方法,则使用默认使用 this 作为调用者;如果被调方法是静态方法,则默认使用类作为调用者。也就是说,表面上看起来某些方法可以被独立执行,但实际上环视使用this或者 类 作为调用者。

Java的参数传递中可以引用传递吗?

不可以,Java的参数传递方式只有一种,值传递。所谓值传递,就是将实际参数值的副本(复制品)传入方法内,而参数本身不会受到影响。

我们有时候见到 使用 参数传递 某些对象。看起来好像是引用传递,但其实不是,这里传递的也只是一个对象在内存中的的地址而已,并不是真正的把 对象引用传递过去。

形参个数可变的参数是什么?

Jdk1.5 之后,Java允许定义形参个数可变的参数,从而允许为方法指定数量不确定的形参。如果在定义方法时,在最后一个形参的类型后增加三点 (…),则表明该形参可以接受多个参数值,多个参数值被当成数组传入。


public class MyClass {
    private static int age;

    public static void main(String[] args) {
        Demo(2,3,4);
        Demo(new int[]{12,2});
    }

    static void Demo(int... a2) {
        for (int i : a2) {
            System.out.println(i);
        }
    }

}

但需要注意的是,长度可变的形参只能处于形参列表的最后。一个方法最多只能包含一个长度可变的形参。长度可变的形参本质就是一个数组类型的形参,因此调用包含一个长度可变形参的方法时,这个长度可变的形参即可以传入多个参数,也可以传入一个数组。

为什么方法的返回值类型不能用于区重载的方法?

对于 int f()和 void f() 两个方法,如果 int a=f(),系统可以识别是调用返回值类型为 int 的方法;但Java 调用方法时 可以忽略 方法的返回值,如果直接调用 f(),那么谁有知道到底是调用了那个方法呢?

为什么需要添加set get方法?

Java类里实例变量的 set 和get方法有着非常重要意义,例如,某个类里包含了一个名为 abc 的实例变量,则其对应的 set和get 方法名应为setAbc() 和 getAbc().

如果一个类 Java 类的每个实例 变量都被使用 private 修饰,并为每个实例变量都提供了 public 修饰的 set 和 get方法,那么这个类就是一个 符合 JavaBean 规范的类,因此,JavaBean 总是一个封装良好的类。

一个类常常就是一个小的模块,应该只让这个模块公开必须让外界知道的内容,而隐藏其他的一切内容。进行程序设计时,应尽量避免一个模块直接操作和访问另一个模块的数据,模块设计追求 高内聚(尽可能把模块之间的内部数据,功能实现细节隐藏在模块内部独立完成,不允许外部直接干预),低耦合(仅暴露少量的方法给外部使用)。

构造器创建Java对象的途径,是不是说构造器完全负责 Java对象?

不是,构造器是创建 Java 对象的重要途径,通过 new 关键字调用构造器时,构造器也确实返回了 该类的对象,但这个对象并不是完全由构造器负责创建的。实际上,当程序员调用构造器时,系统会先为该对象分配内存空间,并为这个对象执行默认初始化,这个对象已经产生了——这些操作系统在构造器的执行前就都已经完成了。也就是说,当系统开始执行构造器的执行体之前,系统已经创建了一个对象,只是这个对象还不能被外部程序访问,只能在该构造器中通过this 来引用。当构造器的执行体结束后,这个对象作为构造器的返回值而被返回,通常还会赋给另一个引用类型的变量,从而让外部程序可以访问该对象。

super限定的用处?

如果需要在子类方法中调用 父类被覆盖的实例方法。则可使用 super 限定来调用父类被覆盖的实例方法。

package com.example.javatest;

public  class Demo extends A{
    public static void main(String[] args) {
        new Demo().test();
    }
    public void test(){
        super.test();
        System.out.println("Demo");
    }
}
class  A{
    public void test(){
        System.out.println("A");
    }
}

什么是多态?

Java 引用变量有两个类型,一个是编译型类型,一个是运行时类型,编译时类型由声明该变量时使用的类型决定,运行时类型由实际赋给变量的对象决定。如果编译时类型和运行时类型不一样。就可能出现所谓的多态。

当把一个子了i对象那个直接赋给父类引用变量时,运行时调用该引用变量的方法是,其方法行为总是表现出子类方法的行为特征,而不是父类方法的行为特征。

instanceof 运算符是干什么的?

判断是否是可以成功转换

instanceof 运算符的前一个操作数通常是一个 引用类型变量,后一个操组数通常是一个类(也可以是接口,可以把接口理解成一种特殊的类),它用于判断前面的对象是否是后面的类,或者其子类,实现类的实例。如果是,返回 true,否则 返回false

public class Demo {

    public static void main(String[] args) {
        Object a=new String("123");
        if (a instanceof Integer) {
            System.out.println("123");
        }
    }
}

谈谈你对继承的理解?

继承最大的好处就是类的复用性,子类可以直接调用父类的所有成员变量和方法。但是继承带来高度复合的同事,也带来了一个严重的问题:继承严重破坏了父类的封装性。Java 里对封装的定义是:每个类都应该封装它内存信息和实现细节,而只暴露必要的方法给其他类使用。但在继承关系中,子类可以直接访问父类的成员变量(内部信息)和方法,从而造成子类和父类的严重耦合。

所以我们在使用继承的时候。首先得明白:子类是一种能特殊的父类。

对于具备以下条件之一才使用继承,否则使用 组合 也能实现类的复用。

  • 子类需要额外增加属性,而不仅仅是属性值的改变。
  • 子类需要增加自己独有的行为方式(包括增加新的方法或重写父类的方法)

谈谈你对组合的理解?

把一个类当做一个类的组合成分,从而允许新类直接复用该类的 public 方法,这种就是组合。

一般用到组合的地方,用继承也可以实现,简单来说,继承是对已有类的一番改造,以此获得一个特殊的类。简而言之,就是将一个较为抽象的类改造成能适用于某些特定需求的类,比如在原来基础上增加别的成员变量或者方法等。反之,如果两个类之间有明确的整体,部分的关系,此时就应该采用组合关系来实现复用。

总之,继承要表达的是 (is-a)的关系,而组合表达的是 有 (has-a)的关系。(is-a代表的是继承关系,has-a代表的是对象和它成员的从属关系)

什么是自动装箱?

自动装箱,就是可以把一个基本类型变量直接赋给对应包装类变量,或者赋给Object变量,(Object是所有类的父类,自诶对象可以直接符给父类变量),自动拆箱则则与之相反,允许直接·把包装类对象直接赋给一个对应的基本类型变量。

简单讲一下==与 equals 方法有什么不同?

Java程序测试两个变量是否相等有两种方式,一种是利用== 运算符,另一种就是利用 equals方法,当时用 判断两个变量是否相等时,如果两个变量时基本类型变量,且都是数据类型,则只要两个变量的值相等,就将返回 true。但对于引用类型变量,只要他们指向同一个对象, 判断才会返回true,== 不可用与比较类型上没有父子·关系的两个对象。euqals 判断的是引用对象里包含的字符序列是否相同,相同就返回true,

简单讲一下 final 修饰符

final修饰符可用于修饰类,变量和方法。final 修饰的变量度不可被改变,一旦获得了初始值,该 final 变量的值就不能被重新赋值。final修饰的成员变量必须由程序员显示的指定初始值,因为系统不会为 final 修饰的变量隐式初始化。

final修饰基本引用类型和运用类型变量有什么区别?

当时用final 修饰基本类型数据时,不能对基本类型变量重新赋值,因此基本类型变量不能被改变。但对于引用类型变量而言,它保存的仅仅是一个引用,final 只保证这个引用类型变量所引用的地址不会改变,即一直引用同一个对象,但这个对象完全可以发生改变。

为什么被final修饰的变量 被称为 执行宏替换 的变量?

当定义final 变量时就为该变量指定了初始值,而且该初始值可以在编译期间确定下来,那么这个 final 变量本质上就是一个 宏变量 ,编译器会把程序中所有用到该变量的地方直接替换为该变量的值。

谈谈你对抽象类与抽象方法的理解?

  1. 抽象类必须是 abstact 修饰符来修饰,抽象方法也必须使用 abstract 修饰符来修饰,抽象方法不能有方法体。
  2. 抽象类·不能被实例化,无法使用 new 关键字来调用 抽象类的构造器创建抽象类的实例。
  3. 抽象类可以包含成员变量,方法(普通方法和抽象方法都可以),构造器,初始化块,内部类(接口,枚举)5种成分。抽象类的构造器不能用于创建实例,主要是用于被其子类调用。
  4. 含有抽象方法的类(包括直接定义了一个抽象方法;或继承了一个抽象方法,但没有完全实现父类包含的抽象方法;或实现了一个接口,但没有完全实现接口包含的抽象方法三种情况)只能被定义成抽象类。

需要注意的是:

  • static 和 abstract 不能同时修饰某个方法。
  • static 和 abstract 可以同时修饰内部类
  • abstract 关键字修饰的方法必须被其子类重写才有意义,否则这个方法将永远不会有方法体。

抽象类的作用是什么?

抽象类不能创建实例,只能当成父类来继承。从语义的角度来说,抽象类是从多个具体类中抽象出来的父类,他具有更高层此的抽象。从多个具有相同特征的类中抽象出了一个抽象类,以这个抽象类作为其子类的模板,从而避免了子类设计的随意性。

抽象类体现的就是一种模板模式的设计,抽象类作为多个子类的通用模板,子类在抽象类的基础上进行扩展,改造,但子类总体上会大致保留抽象类的行为方式。

什么是接口?

在java8里,允许为接口定义默认方法,类方法。

接口从宏观上来说,是从多个相似类中抽象出来的规范,接口不提供任何实现。说简单点,接口反映的是一类事物的方法。

接口里可以包含成员变量(只能是静态常量),方法(只能是抽象实例方法,类方法或默认方法),内部类(内部接口,枚举)

接口支持多继承。

谈谈你对接口和抽象类的理解?

接口和抽象类具有如下相同特征:

  • 接口和抽象类都不能被实例化,他们都位于继承树的顶端,用于被其他类实现和继承。

  • 接口和抽象类都可以包含抽象方法,实现接口或继承抽象类的普通子类都必须实现这些抽象方法。

  • 但接口和抽象类之间的差别非常大,这种差别主要体现在二者的设计目的上。

  • 接口体现的是一种规范。对于接口的实现者而言,接口规定了实现者必须向外提供哪些服务(以方法的形式提供);对于接口的调用者而言,接口规定了调用者可以调用哪些服务,以及如何调用这些服务(就是如何来调用方法)。当一个程序中使用接口时,接口时多个模块间的耦合标准。

    从某种程度上来看,接口类似于整个系统的 总纲 ,它制定了系统各模块应该遵循的标准,因此一个系统中的接口不应该经常改变。一旦接口被改变,对整个系统升值其他系统的影响将是辐射性的,导致系统中大部分类都需要改写。

    抽象类作为系统中多个子类的共同父类,它所体现的是一种模板设计。抽象类作为多个子类的抽象父类,可以被当成系统实现过程中的中间产品,这个中间产品已经实现了系统的部分功能(那些已经提供实现的方法),但这个产品依然不能被当成最终产品,必须有跟进异步的完善,这汇总完善可能有几种不同的方式。

  • 在用法上,抽象类和接口也存在如下差别:

    1. 接口里只能包含抽象方法和默认方法,不能为普通方法提供方法实现;抽象类则完全可以包含普通方法。
    2. 接口里不能定义静态方法,抽象类里可以定义静态方法。
    3. 接口里只能定义静态常量,不能定义普通成员变量;抽象类既可以定义普通成员变量,也可以定义静态常量。
    4. 接口i不包含构造器,抽象类里可以包含构造器,抽象类里的构造器并不是用于创建对象,而是让其子类调用这些构造器来完成属于抽象类的初始化操作。
    5. 接口里不能包含初始化块,但抽象类则完全可以包含初始化块。
    6. 接口可以多实现,抽象类只能单继承。

什么是内部类?

将一个类放在另一个类的内部定义,这个定义在其他类内部的类被称为内部类,包含内部类的类也被称为外部类。

内部类的作用如下:

  • 内部类提供了更好的封装,可以把内部类隐藏在外部类之内,不允许同一个包中的其他类访问该类。
  • 内部类成员可以直接访问外部类的私有数据,因为内部类被当成其外部类成员,同一个类的成员之间可以互相访问。但外部类不能访问内部类的实现细节,例如内部类的成员变量。
  • 匿名内部类适合用于创建那些仅需要一次使用的类。

内部类与外部类还与如下区别:

  • 内部类比外部类多使用三个修饰符,pivate ,protected,static 外部类不可以使用这三个修饰符。
  • 非静态内部类不能拥有静态成员。

为什么静态内部的实例方法也不能访问外部类的实例属性?

因为静态内部类是外部类的类相关的,而不是外部类的对象相关的。也就说说,静态内部类对象不是寄生在外部类的实例中,而是寄生在外部类的本身中。当静态内部类对象存在时,并不存在一个被它寄生的外部类对象,静态内部类对象只持有外部类的类引用,没有持有外部类对象访问。如果允许静态内部类的实例方法访问外部类的实例成员,但找不到被寄生的外部类对象,这将引起错误。

Lambda表达式的使用

  • 形参列表.形参列表允许省略形参列表。如果形参列表中只有一个参数,甚至连形参列表的圆括号也可以省略。
  • 箭头 (->)
  • 代码块。如果代码块只包含一条语句,lambda 表达式允许省略代码块的花括号。如果Lambda 代码只有一条return语句,那么省略return 关键字。

遍历list 的方式

  List<String> list=new ArrayList<>();
        list.add("123");
        list.add("123");
        list.add("123");
        list.add("123");
        list.add("123");
        list.forEach(System.out::println);

实现Runnable接口

  new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(123);
            }
        }).start();
        
        new Thread(()-> System.out.println("123")).start();

使用匿名内部类

Runnable runnable=new Runnable() {
    @Override
    public void run() {
        System.out.println(123);      
    }
};

Runnable r1=()-> System.out.println("123");
r1.run();

如下写法

public class MyClass {

    public static void main(String[] args) {
            new MyClass().eat(()-> System.out.println("132"));
            new MyClass().fly(weather -> {
                System.out.println("今天");
            });
            new MyClass().test((a,b)->a+b);
    }
    public void eat(Eatable e){
        System.out.println(e);
        e.taste();
    }
    public void fly(Flyable f){
        f.fly("Petterp");
    }
    public void test(Addable a){
        a.add(5,3);
    }
}
interface Eatable{
    void taste();
}

interface Flyable{
    void fly(String weather);
}

interface Addable{
    int add(int a,int b);
}

什么是枚举?

枚举类是一种特殊的类,他一样可以有自己的成员变量,方法,可以实现一个或者多个接口,也可以定义自己的构造器。一个 Java源文件中最多只能定义一个 public 访问权限的枚举类,且该 Java 源文件也必须和枚举类的类名相同。

枚举类与普通类之间有如下区别:

  • 枚举类可以实现一个或多个接口,使用 enum 定义的枚举类默认集成了 java.lang.Enum 类,而不是默认集成 Object 类,因此枚举类不能显示继承其他父类。其中java.lang.Enum类实现了 JAVA.langSerializable 和 java.lang.Comparable两个接口。
  • 使用Enum 定义,非抽象的枚举类默认会使用 final 修饰,因此枚举类不能派生子类。
  • 枚举类的构造器只能使用 private 访问控制符,如果省略了构造器的访问控制符,则默认使用 private修饰,如果强制指定访问控制符,则 只能 指定 private修饰符。
  • 枚举类的所有实例必须在枚举类的第一行显示列出,否则这个枚举类永远都不能产生实例。列出这些实例时,系统会自动添加 public static fianl 修饰。
枚举类的使用:
public enum SeasonEnum {
    a,b,c,d;
    public String name;
}
class Demo{
    public static void main(String[] args) {
        for (SeasonEnum s:SeasonEnum.values()){
            //打印枚举值的索引
            System.out.println(s.ordinal());
        }
        //如果该枚举对象位于指定枚举对象之后,则返回正整数;
        //如果该枚举对象位于指定枚举对象之前,则返回负整数,否则返回0
        System.out.println(SeasonEnum.valueOf("a").compareTo(SeasonEnum.valueOf("b")));

        //创建枚举类对象
        SeasonEnum seasonEnum=Enum.valueOf(SeasonEnum.class,"a");
        seasonEnum.name="Petterp";
        System.out.println(seasonEnum.name);
    }
}

枚举类也可以用于 switch

枚举类带构造器的使用

public enum SeasonEnum {
    //此处的枚举值必须调用对应的构造器来创建
    MALE("男"), FEmale("女");
    private final String name;

    //枚举类的构造器只能使用private修饰
     SeasonEnum(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}

class Demo {
    public static void main(String[] args) {
        System.out.println(SeasonEnum.FEmale);
    }
}

以上代码如同于以下代码

public  static  final SeasonEnum MALE=new SeasonEnum("男");
public  static  final SeasonEnum FEmale=new SeasonEnum("女");

在枚举类中列出枚举值是,实际上就是调用构造器创建枚举类对象,只是这里无须使用new 关键字,也无需显示调用构造器。前面列出枚举值是无须传入参数,甚至无须使用括号,仅仅是因为前面的枚举类包含无参数的构造器。

实现接口的枚举类

枚举类也可以实现一个或多个接口,与普通类实现一个或多个接口完全一样,枚举类实现一个或多个接口时,也需要实现该接口所包含的方法。

public enum SeasonEnum implements Port {
    //此处的枚举值必须调用对应的构造器来创建
    MALE("男") {
        @Override
        public void info() {
            System.out.println("这个枚举值代表男性");
        }
    },
    FEmale("女") {
        @Override
        public void info() {
            System.out.println("这个枚举值代表女性");
        }
    };
    private final String name;

    //枚举类的构造器只能使用private修饰
    SeasonEnum(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}

class Demo {
    public static void main(String[] args) {
       SeasonEnum.FEmale.info();
    }
}

上面的代码看起来有些奇怪,当创建 MALE 和 FEMALE 两个枚举值时,后面又紧跟了一对花括号,这队花括号包含了一个 info 方法定义。花括号部分实际上就是一个类体部分,在这种情况下,当创建 MALE,FEMALE 枚举值是,并不是直接创建 SeasonEnum枚举类的实例,而是相当于创建 SeasonEnum的匿名子类的实例。因为粗体字括号部分实际上是匿名内部类的类体部分,所以这个部分的代码语法与匿名内部类语法大致相似,只是它依然是枚举类的匿名内部类。

枚举类不是用 final 修饰吗,为什么还可以派生子类?

并不是所有的枚举类都是用了 final 修饰,非抽象的枚举类才默认使用 final 修饰,对于一个抽象的枚举类而言,只要它包含了抽象方法,它就是抽象枚举类,系统会默认是使用 abstart修饰。

枚举类可以包含抽象方法吗?
public enum Operation {
    Plus {
        @Override
        public double eval(double x, double y) {
            return 0;
        }
    },
    MINUS{
      public double eval(double x,double y){
          return x-y;
      }
    };
    //为枚举类定义一个抽象方法
    //这个抽象方法由不同的枚举值提供不同的实现
    public abstract  double eval(double x,double y);

    public static void main(String[] args) {
        System.out.println(Operation.Plus.eval(3,4));
    }
    
    //枚举类里定义抽象方法是不能使用 abstart 关键字将枚举类定义成抽象类(因为系统自动会为它添加 abstart 关键字),但因为枚举类需要显示创建枚举值而不是作为父类,所以定义每个枚举值时必须为抽象方法提供实现,否则将出现编译错误。
发布了97 篇原创文章 · 获赞 643 · 访问量 9万+

猜你喜欢

转载自blog.csdn.net/petterp/article/details/100637005