Java面向对象基础
提问题
- 谈谈你对面向对象的理解
- 分别阐述以下关键字作用于类、方法、实例域(field)的效果,new、static、final、控制可见性的4个访问修饰符、native、instanceof
- 方法重载和重写
- Java采用的是值调用还是引用调用?常见的构造字符串在分配内存的区别
- 谈谈你对继承的理解
- this与supper异同,以及构造器
- 谈谈你对多态的理解
- Object类,它是谁的父类?Equals()方法与HashCode(),以及wait()和notify()
- 枚举是啥?怎么定义?常用方法
- 接口是什么?怎样定义及实现?接口可以继承接口吗?接口中可以定义变量吗?接口中可以有static方法吗?
- 接口与抽象类
- 内部类是啥?怎么用?
- 泛型怎么理解?举个栗子。泛型类 泛型方法 泛型。限制泛型:extends、supper、?和 : 的意义
面向对象理解
照本宣科地讲一下,通过以下几段抽象的描述来简单体会以下面向对象:
OOP,面向对象程序设计,数据是第一位,然后再考虑操作数据的算法;
具体我们的通过其三大特性来理解面向对象,三大特点:封装、继承、多态;
类与对象,类是构造对象的模板和蓝图,由类构造对象的过程称为创建类的实例
封装:(也称为数据隐藏),封装的关键在于绝对不能让类中的方法直接地访问其他类的实例域。
程序仅仅通过对象的方法域对象数据进行交互,最简单的就是Java Bean,从形式看,封装不过是将数据和行为组合在一个包里面,并对对象的使用者隐藏了数据的实现方式
阐述以下关键字
new、static、final、控制可见性的4个访问修饰符、native、instanceof
new
new关键字返回的是一个引用,一般是这个对象的地址。一般是new一个对象
final
- 类:表示这个类不能被继承(绝育),不允许这个类被扩展(例如String类)
- 实例域:一旦被创建为对象,new后,就不可修改
- 方法:表示这个方法不能被子类重写(绝育)
static
static 类:静态内部类 实例域:属于类层面的,一旦被定义,所有被这个类创建的对象的值是一样的,类似于单例模式,可以修改;常与final static修饰为常量;用花括号修饰一段代码,表示这段代码是静态的,调用类的时候初始化用
方法:静态方法,直接调用类名就可执行方法,不用创建对象,常见的Math.静态方法(参数)。
作用域修饰符
private、public、protected、无修饰(默认)
- private:仅仅本类可见
- public:对所有类都可见
- protected:对本包和所有子类可见
- 默认:仅本包可见
native
copy的别人的
- native是java中的一个关键字,在Java诞生的时候正是c跟c++盛行的时候,想要立足就得能够调用c跟c++的程序,native就是用来实现这个功能的。
- 凡是带了native关键字的,说明java的作用已经达不到了,会去调用底层库。
- native关键字作用与方法上,并且不提供实现体(废话,肯定是其他语言实现的了),它会进入本地方法栈,通过调用JNI接口实现对其他语言代码和代码库的使用。
- 内存中有一块专门开辟的区域:Native Method Stack,登记Native方法。
举个栗子,Object的本地方法,
public native int hashCode() //用本地方法去实现hashCode()函数
instanceof
检测一个对象是否属于某个特定的类,或者检测一个对象是否实现了某个接口
boolean b1 = s1 instanceof String;
重载&重写
- 重载:重新载入,参数不同,方法名相同,常用于构造器
- 重写:重新写一遍,子类重新写父类的方法,方法名和参数相同,常用于多态。
值调用&引用调用
- 对于基本数据类型,java是值引用,相当于形参
- 对于对象,也是值调用,但因为所有对象变量指向的都是同一个对象(Java堆中),所以可以通过形参改变对象的状态,类似于实参
需要稍微了解一下JVM加载对象时候是怎样分配内存的:String构造在分配内存的区别
继承
- 本质上是复用父类的方法和域,并且可以扩展这些方法和域
- 关键字extends:继承,扩展
this & super
同:
- 用于引用隐式参数 this.参数;
- 在一个构造器内调用该类的其他构造器,直接this(构造器参数,或者不含参)即可,或supper(参数)即可,常用于子类构造器调用父类的构造器,子类构造器的第一行代码默认是,supper(父类构造器参数)。
异:
this表示本类中,supper表示父类中
扩展:构造器详解
傻子都知道的(哈哈哈):没有定义构造器的话,会有一个默认的构造器。如果定义了一个有参的构造器,那么这个默认的无参构造器会失效(没有显示地定义无参构造器)。
多态
-
多态首先是建立在继承的基础上的,先有继承才能有多态。多态是指不同的子类在继承父类后分别都重写覆盖了父类的方法,即父类同一个方法,在继承的
-
类中表现出不同的形式。
-
字面的意思就是方法的“多种状态”
-
简单的说,就是一句话:允许将子类类型的指针赋值给父类类型的指针。
代码形式:
父类(或者接口) 对象名 = new 子类();
简单的说,建立一个父类对象的引用,它所指对象可以是这个父类的对象,也可以是它的子类的对象。Java中当子类拥有和父类同样的函数,当通过这个父类对象的引用调用这个函数的时候,调用到的是子类中的函数。
JVM内存分配,父类 = new 子类();在内存中开辟子类的内存,子类的内存由父类和子类共同组成。详见下图(盗的别人的),原作请戳此
Object是个啥
所有类的鼻祖
Equals()
==与Equals()的区别,==表示两个对象变量引用的对象地址相同,Equals()表示的是两个对象的实例域值相同。
Equals()方法,一般是先==判断是否地址相同,然后再判是否为null或者是否为同一个类,最后再判值时都相同
HashCode()
散列存储,又称哈希存储,是一个本地方法,每个对象可以计算出一个哈希码,根据一个对象的哈希码就可以确定该对象应该存储在哪个区域,大大减少查询匹配元素的数量
wait()
导致当前的线程等待,直到其他线程调用此对象的notify( ) 方法或 notifyAll( ) 方法,或者指定的时间过完
notify()
唤醒在此对象监视器上等待的单个线程
notifyAll()
唤醒在此对象监视器上等待的所有线程
枚举
一个集的枚举是列出某些有穷序列集的所有成员的程序,一一列举,通过绑定序号和常量,java不包含数值,优化成为一个类;
在Java中,枚举是个类,构造的时候用enum关键字,直接在类内定义实例域即可。
public enum Room{ONE,TWO,THREE};
Room one = Room.ONE;
//所有的枚举类型都是Enum的子类,类似所有的类都是Object的子类,我们可以使用Enum的方法去进行一些操作
toString()//返回枚举常量名
String s1 = one.toString();
//values():返回返回一个包含全部枚举值的数组,类型为枚举类型*
Room[] values = Room.values();
//Enum的静态方法:返回具有指定名称的指定枚举类的枚举常量,底层是反射原理
Room one1 = Enum.*valueOf*(Room.class, "ONE");
接口
接口:一些方法的特征集合
- 接口定义的所有方法默认都是public abstract,所以这两个修饰符不需要写出来(写不写效果都一样)
- 接口主要用来描述类具有什么样的功能,并不给出每个功能的具体实现
- 使用interface创建
- 一个类可以实现一个或多个接口,用关键字 implements
- 如果类遵从某个特定的接口,那么就履行这项服务,一个类继承了某个方法就得实现接口中所有的方法
//一个烂的不能再烂的栗子
interface Animal{
void go();
}
-
接口可以实现接口
-
接口实现接口用extends,也可以叫继承吧?
-
实现接口的抽象方法用implements
-
在使用的时候,实例化的对象永远只能是某个具体的子类,但总是通过接口去引用它,因为接口比抽象类更抽象:
List list = new ArrayList(); // 用List接口引用具体子类的实例*
Collection coll = list; // 向上转型为Collection接口*
Iterable it = coll; // 向上转型为Iterable接口
-
接口中可以定义常量,默认修饰为public static final,只能定义常量
-
用abstract创建抽象类,用abstract修饰抽象方法
-
抽象类的本质是面向抽象编程
面向抽象编程的本质就是:
- 上层代码只定义规范(例如:abstract class Person);
- 不需要子类就可以实现业务逻辑(正常编译);
- 具体的业务逻辑由不同的子类实现,调用者并不关心。
这种引用抽象类的好处在于,我们对其父类进行方法调用,并不关心父类型变量的具体子类型
Java 8 新特性
Java 8 中,接口中不仅可以定义抽象方法,可以为接口添加静态方法和默认方法
- 静态方法:使用static关键字修饰。可以通过接口直接调用静态方法,并执行其方法体
- 默认方法:使用default关键字修饰。可以通过类来调用
接口&抽象类
内部类(类不内)
如果一个类定义在另一个类的内部,这个类就是Inner Class
内部类:我是参照这篇博客总结的:详谈Java内部类
- 成员内部类:内部类调用外部类,*当和外部类属性名重叠时,可通过外部类名.this.属性名,调用外部类方法——直接写方法名即可;外部类调用内部类,就是常规的调用;*其他类使用成员内部类:先创建外部类,再创建内部类
Outer outer = new Outer(); Outer.Inner inner=outer.new Inner("a");
- 静态内部类:用static修饰内部类,然后就可以直接用内部类的静态方法调用外部类有关的static信息,静态内部类的方法只能访问外部类的static关联的信息。
//访问静态内部类的静态方法,Inner类被加载,此时外部类未被加载,独立存在,不依赖于外围类。
Outer.Inner.innerStaticShow();
//访问静态内部类的成员方法
Outer.Inner oi = new Outer.Inner();
oi.innerShow();
- 局部内部类:*成员方法,内部定义局部内部类,局部内部类只能在方法内使用;其他的调用与成员内部类相同,访问同名外部的变量仍然为Outer.this.变量名;*可以直接访问方法内的局部变量和参数(有限制),但是不能更改
- 匿名内部类:本质上是局部内部类,讲到这里,lambda表达式请求出战
泛型
- 泛型是一种编程思想,泛型程序设计思想
- 泛型就是编写模板代码来适应任意类型;
- 泛型的好处是使用时不必对类型进行强制转换,它通过编译器对类型进行检查;
- 泛型,即“参数化类型”。泛型的本质是为了参数化类型(在不创建新的类型的情况下,通过泛型指定的不同类型来控制形参具体限制的类型)
我的理解:可以用符号代替不同的对象;在使用类或者方法的时候,用一些符号来代替传统的参数变量,可以用符号代替不同的对象(类),从而达到灵活变通的结果,那就得涉及继承,父子类型转换、不同变量的区分等问题
举个栗子:
Generic<Integer> aaa;//看到这个玩意不要害怕,它就是一个类, Generic<Integer>就是一个类,没有其他的意思,<>代表泛型,我以前把它理解为数组了[捂脸]
ArrayList<String> objects1 = new ArrayList<String>();
ArrayList<Integer> objects2 = new ArrayList<Integer>();
Generic genericInteger = new Generic(123456);
我们可以使用泛型在定义时传入不同的参数,从而定义不同的类型;
泛型只在编译阶段有效,其他时候(例如运行阶段)都是Object类,在运行时JVM是不知道泛型信息的
泛型类,定义时在后面加上,符号可以随便
- 泛型接口,定义时在后面加上,符号可以随便
- 子类可以选择实现接口时候具体化这个泛型,也可以再继续使用泛型T
- 泛型方法,仅仅返回值为T的这种方法并不是泛型方法,是一般方法,只不过返回值为T
- 在返回值前加上,声明这是一个泛型方法
extends & supper :
- 我们可以使用类似定义泛型类时表示:泛型类型限定为Number以及Number的子类。
- 注意哦:使用extends通配符表示可以读,不能写。
- <? extends Number>限制泛型的上界
- <? supper Integer>限制泛型的下界
- 注意哦:使用supper通配符表示只能写,不能读
在泛型方法中添加上下边界限制的时候,必须在权限声明与返回值之间的上添加上下边界,即在泛型声明的时候添加
?
类型通配符一般是使用?代替具体的类型实参,注意了,此处?
是类型实参,而不是类型形参,当然用其他字母也可,只不过我们一般用 ? ,?就是可以接受所有的类型参数
review与反思:
学一个技术:
- 首先懂它是做什么的,也就是应用场景;
- 然后先学会怎么用,具体代码怎么实现的;
- 最后了解一下它的原理,最好与已知的知识联系起来。