第九章
1.抽象类和接口的区别?
说这个问题之前,必须得说明抽象类和抽象方法。首先抽象方法是必须在抽象类中,不能在普通方法中。但是,抽象类可以包含一个或多个抽象方法,也可以没有抽象方法。抽象方法中也可以存在不是抽象的方法。
抽象类:(为了继承而存在,本身是没什么意义)
1.抽象方法必须为public或者protected(因为使用了private,则不能被子类所继承,子类无法实现该方法)。
2.抽象类是不能直接用来创建对象。
3.如果一个类继承于一个抽象类,子类必须实现父类的抽象方法。如果子类没有完全实现父类的抽象方法,那这个子类也必须定义为抽象类。
接口:
1.在接口中显示地将方法定义为public,即使不这么做,也会默认这么做的。当实现一个接口时,必须将接口里的方法定义为public。
2.接口里变量只能定义为public static final。
3.接口中的方法只能为抽象的。
区别:
分语法层面和设计层面两个层面说
语法层面:1.抽象类可以提供成员方法的实现细节,而接口中只能存在抽象方法。
2.抽象类中的成员变量可以是各种类型,但接口中的成员变量必须由public static final修饰。
3.抽象类中可以有静态代码块和静态方法,但接口中不能含有静态代码块和静态方法。
4.一个类只能继承一个抽象类,但是一个类却可以实现多个接口。
5.实现接口的非抽象类必须要实现该接口的所有方法。抽象类可以不用实现。
设计层面:1.抽象类是“是不是”的关系,而接口是“有没有”的关系。
2.抽象类作为许多子类的父类是一种模板式设计,而接口则是辐射式设计。
2.C++有多重继承, 虽然JAVA没有多重继承的类,但有多重接口
这里引用《JAVA编程思想》的话:“C++中,组合多个类接口的行为被称为多重继承,但它会让你揹负很多的包袱,因为每个类都有一个具体的实现。”
怎么理解?为什么JAVA中不允许多重继承的类?
abstract class a{
abstract void b();
void c(){
System.out.println("c()");
}
abstract void d();
abstract void e();
abstract void f();
}
abstract class b extends a{
abstract void b();
abstract void d();
abstract void e();
void f(){
System.out.println("f()");
}
/*abstract void f();*/
}
abstract class c extends a{
abstract void b();
abstract void d();
abstract void e();
abstract void f();
}
public class Main {
public static void main(String args[]){
}
}
以上代码中,b和c类都继承了a类,并且都有f的方法,只不过b类中f方法已经具体实现了,而c类中f方法只是抽象方法。只是你要是多重继承b类和c类,当调用f方法时是调用b类和c类的f方法?这里就出现问题了,但如果是实现接口就不会,因为接口中所有方法都是抽象方法都没有具体实现。
除此之外,JAVA多重继承除了使用接口来实现外,还可以使用内部类。这个后面说
3.设计模式 适配器模式 工厂设计模式 策略设计模式
这个内容在之后补充
第十章
1.什么叫内部类 ?内部类的一些特点,内部类和外部类的关系
内部类就是在一类里又有一个类。内部类可以访问其外围所有成员,而不需要任何特殊条件,并且还拥有拥有外部类所有元素的访问权。为什么可以做到如此?编译器会默认为成员内部类添加了一个指向外部类对象的引用,这样内部类就有了外部类对象一样访问所有元素的权利。在拥有外部类对象之前,是不能创建内部类对象的。
2.为什么要用内部类
解决JAVA多重继承的问题。因为JAVA一个类只能继承一个类(单继承),之前接口解决了一部分这个问题,一类可以实现多个接口,但还是不方便,实现一个接口就必须实现它里面的所有方法。而内部类就解决了这个问题,一个类可以有好多内部类,每个内部类都可以继承一个类,变相地实现了多重继承。
出个题:25 20 18
class Outer {
public int age = 18;
class Inner {
public int age = 20;
public viod showAge() {
int age = 25;
System.out.println(age);//空1
System.out.println(this.age);//空2
System.out.println(Outer.this.age);//空3
}
}
}
3.怎么在外部类的外面创建内部类使用
格式:
//成员内部类不是静态的:
外部类名.内部类名 对象名 = new 外部类名.new 内部类名();
//成员内部类是静态的:
外部类名.内部类名 对象名 = new 外部类名.内部类名();
4.匿名内部类
我们在开发的时候,会看到抽象类,或者接口作为参数。而这个时候,实际需要的是一个子类对象。如果该方法仅仅调用一次,我们就可以使用匿名内部类的格式简化。
举个例子:
public interface Demo {
void demoMethod();
}
public class MyDemo{
public test(Demo demo){
System.out.println("test method");
}
public static void main(String[] args) {
MyDemo md = new MyDemo();
//这里我们使用匿名内部类的方式将接口对象作为参数传递到test方法中去了
md.test(new Demo){
public void demoMethod(){
System.out.println("具体实现接口")
}
}
}
}
上述的例子就描述了为什么需要匿名内部类,起始就是简化代码,不用你去起名字了。在Java中,通常就是编写一个接口,然后你来实现这个接口,然后把这个接口的一个对象作以参数的形式传到另一个程序方法中, 然后通过接口调用你的方法,匿名内部类就可以很好的展现了这一种回调功能。
5.局部内部类和静态内部类
1.局部内部类:就是定义在一个方法或者一个作用域里面的类
class Outer {
public void method(){
class Inner {
}
}
}
局部内部类的特点:只能在所在的方法里被使用,见下面的例子
//在局部位置,可以创建内部类对象,通过对象调用和内部类方法
class Outer {
private int age = 20;
public void method() {
final int age2 = 30;
class Inner {
public void show() {
System.out.println(age);
//从内部类中访问方法内变量age2,需要将变量声明为最终类型。
System.out.println(age2);
}
}
Inner i = new Inner();
i.show();
}
}
2.静态内部类:static是不能用来修饰类的,但是成员内部类可以看做外部类中的一个成员,所以可以用static修饰,这种用static修饰的内部类我们称作静态内部类,也称作嵌套内部类.
特点:即使没有外部类对象,也可以创建静态内部类对象,而外部类的非static成员必须依赖于对象的调用,静态成员则可以直接使用类调用,不必依赖于外部类的对象,所以静态内部类只能访问静态的外部属性和方法。
class Outter {
int age = 10;
static age2 = 20;
public Outter() {
}
static class Inner {
public method() {
System.out.println(age);//错误
System.out.println(age2);//正确
}
}
}
public class Test {
public static void main(String[] args) {
Outter.Inner inner = new Outter.Inner();
inner.method();
}
}
第十一、十五章泛型总结
泛型概念:泛型是一种未知的数据类型,当我们不知道使用什么数据类型的时候,可以使用泛型。
不使用泛型
1.为什么要有泛型
好处:可以储存任意类型的数据 坏处:不安全、可能出现异常
使用泛型
好处:1.避免了类型转换的麻烦,存入的数据是什么类型,读取出来也是相应的类型。2.把运行期的异常,放到了编译期来排查。
坏处:泛型规定的存入数据类型,就不能存其他类型的数据。
举个例子:比如我们写一个座标值,那这个座标可以整数,也可以包含小数的,但是它的意义都是表示座标的。
如果我们不用泛型,那就得有一个数据类型,写一个相关的类:
//设置Integer类型的点座标
class IntegerPoint{
private Integer x ; // 表示X座标
private Integer y ; // 表示Y座标
public void setX(Integer x){
this.x = x ;
}
public void setY(Integer y){
this.y = y ;
}
public Integer getX(){
return this.x ;
}
public Integer getY(){
return this.y ;
}
}
//设置Float类型的点座标
class FloatPoint{
private Float x ; // 表示X座标
private Float y ; // 表示Y座标
public void setX(Float x){
this.x = x ;
}
public void setY(Float y){
this.y = y ;
}
public Float getX(){
return this.x ;
}
public Float getY(){
return this.y ;
}
}
但如果使用泛型了:变成同一数据类型Object,兼容了所有的数据类型。
class ObjectPoint{
private Object x ;
private Object y ;
public void setX(Object x){
this.x = x ;
}
public void setY(Object y){
this.y = y ;
}
public Object getX(){
return this.x ;
}
public Object getY(){
return this.y ;
}
}
在使用的时候,只要创建这个类对象,比如我们要存入Integer这个整数的包装类
ObjectPoint integerPoint = new ObjectPoint();
integerPoint.setX(new Integer(100));
Integer integerX=(Integer)integerPoint.getX();
2.泛型类
基本使用和格式,以下面为例:
//定义
class Point<T>{// 此处可以随便写标识符号 比如E
private T x ;
private T y ;
public void setX(T x){//泛型作为参数
this.x = x ;
}
public void setY(T y){
this.y = y ;
}
public T getX(){//泛型作为返回值
return this.x ;
}
public T getY(){
return this.y ;
}
};
//IntegerPoint使用
Point<Integer> p = new Point<Integer>() ;
p.setX(new Integer(100)) ;
System.out.println(p.getX());
//FloatPoint使用
Point<Float> p = new Point<Float>() ;
p.setX(new Float(100.12f)) ;
System.out.println(p.getX());
普通类构造函数是这样的:Point p = new Point() ; 而泛型类的构造则需要在类名后添加上<String>,即一对尖括号,中间写上要传入的类型。同时在类申明时也要写上<>,例如class Point<T>。在泛型类创建自己对象的时候,就必须指明泛型具体是什么数据类型,比如是Integer或者Float。
这里的SetX方法并不是泛型方法,只是它的参数里有泛型。
3.泛型接口
interface Info<T>{ // 在接口上定义泛型
public T getVar() ; // 定义抽象方法,抽象方法的返回值就是泛型类型
public void setVar(T x);
}
上面就是一个泛型接口。既然是接口,就不能直接使用,需要实现类去实现接口。2种可能,一种实现类本身不是一个泛型类,另一种实现类本身就是一个泛型类。
Ⅰ.非泛型类实现泛型接口
class InfoImpl implements Info<String>{ // 定义泛型接口的子类
private String var ; // 定义属性
public InfoImpl(String var){ // 通过构造方法设置属性内容
this.setVar(var) ;
}
@Override
public void setVar(String var){
this.var = var ;
}
@Override
public String getVar(){
return this.var ;
}
}
public class GenericsDemo24{
public void main(String arsg[]){
InfoImpl i = new InfoImpl("harvic");
System.out.println(i.getVar()) ;
}
};
这里在实现的时候,就规定了T到底是什么,这里是String类型。为什么?因为它是非泛型类,类里面不能含有泛型,得是确定的数据类型。
Ⅱ.泛型类实现泛型接口
泛型接口中泛型变量T一定会在泛型类里,但泛型类中泛型变量不一定只有一个。它们之间是一个包含关系
下面这个例子,泛型类的泛型变量就只有一个,那肯定就是泛型接口中的泛型变量。
interface Info<T>{ // 在接口上定义泛型
public T getVar() ; // 定义抽象方法,抽象方法的返回值就是泛型类型
public void setVar(T var);
}
class InfoImpl<T> implements Info<T>{ // 定义泛型接口的子类
private T var ; // 定义属性
public InfoImpl(T var){ // 通过构造方法设置属性内容
this.setVar(var) ;
}
public void setVar(T var){
this.var = var ;
}
public T getVar(){
return this.var ;
}
}
//使用
public class GenericsDemo24{
public static void main(String arsg[]){
InfoImpl<String> i = new InfoImpl<String>("harvic");
System.out.println(i.getVar()) ;
}
};
下面这个泛型类的泛型变量有三个。包含的情况
class InfoImpl<T,K,U> implements Info<U>{ // 定义泛型接口的子类
private U var ;
private T x;
private K y;
public InfoImpl(U var){ // 通过构造方法设置属性内容
this.setVar(var) ;
}
public void setVar(U var){
this.var = var ;
}
public U getVar(){
return this.var ;
}
}
//使用
public class GenericsDemo24{
public void main(String arsg[]){
InfoImpl<Integer,Double,String> i = new InfoImpl<Integer,Double,String>("harvic");
System.out.println(i.getVar()) ;
}
}
3.泛型方法
格式:
修饰符 <泛型> 返回值类型 方法名(参数列表 使用泛型){
}
下面说几个注意点:
1.泛型方法所在类既可以是泛型类,也可以不是,两者没关系。
2.static方法无法使用泛型类中的变量参数,所以如果static方法想有使用泛型能力,那它本身必须就是泛型方法。
3.在泛型类创建一个新对象的时候,必须要指定泛型具体的数据类型,而泛型方法是不需要的。
下面就具体举一些实例:
public class StaticFans {
//静态函数
public static <T> void StaticMethod(T a){
Log.d("harvic","StaticMethod: "+a.toString());
}
//普通函数
public <T> void OtherMethod(T a){
Log.d("harvic","OtherMethod: "+a.toString());
}
}
在一个非泛型类里,定义了两个泛型方法,只不过一个是静态方法,一个是普通方法,但定义方法时,没有区别。只是在调用方法时有一点区别。
下面就是调用:
//静态方法
StaticFans.StaticMethod("adfdsa");//使用方法一
StaticFans.<String>StaticMethod("adfdsa");//使用方法二
//常规方法
StaticFans staticFans = new StaticFans();
staticFans.OtherMethod(new Integer(123));//使用方法一
staticFans.<Integer>OtherMethod(new Integer(123));//使用方法二
静态方法调用不需要创建类对象,直接类名.方法名,上面静态和常规方法都有两种调用方式,但推荐都是用第二种,这样可以很清楚地反应你这个泛型具体是什么数据类型。
4.泛型擦除
Java的泛型不同于C++,JAVA的泛型是伪泛型。这是因为Java在编译期间,所有的泛型信息都会被擦掉,如在代码中定义List<Object>
和List<String>
等类型,在编译后都会变成List
,JVM看到的只是List,而由泛型附加的类型信息对JVM是看不到的。Java编译器会在编译时尽可能的发现可能出错的地方,但是仍然无法在运行时刻出现的类型转换异常的情况。
举个例子:
public class Test {
public static void main(String[] args) {
ArrayList<String> list1 = new ArrayList<String>();
list1.add("abc");
ArrayList<Integer> list2 = new ArrayList<Integer>();
list2.add(123);
System.out.println(list1.getClass() == list2.getClass());
}
}
这里会打印出来的是true,虽然一个是ArrayList<String>,另一个是ArrayList<Integer>。但在运行时,都会被擦除成ArrayList,所以将它们俩比较是否一致时,会得到肯定的答复。这里肯定就有疑问,为什么JAVA中泛型在运行过程要擦除呢?
其实是无奈之举,因为JAVA最初没有泛型,加入泛型这个新特性之后,为了对老代码的支持,所以才有在运行时,泛型擦除的情况。
下面来看几种写法:
ArrayList<String> list = new ArrayList<String>();
ArrayList<String> list1 = new ArrayList(); //第一种 情况
ArrayList list2 = new ArrayList<String>(); //第二种 情况
第1行最标准的写法,这种写法肯定没有问题,可以正常使用这个泛型。第2行可以行,实现的效果与第1行是完全一致。第3行写法是错误的,无法编译。因为类型检查就是编译时完成的,new ArrayList()
只是在内存中开辟了一个存储空间,可以存储任何类型对象,而真正设计类型检查的是它的引用(等号左边)list1还是list2。
同样再看两种写法:左右泛型不一致,但是Object类是String类的原始类。
ArrayList<String> list1 = new ArrayList<Object>(); //编译错误
ArrayList<Object> list2 = new ArrayList<String>(); //编译错误
上面两个都是编译错误,第一个左边引用指向是String,右边开辟的空间存入是Object类,这不就矛盾了。第二个稍微好点,起码存入String类,而左边指向的是Object类,但是这样同样违反了泛型设计的初衷。
5.类型擦除与多态的冲突(难点)
首先,设定一个泛型类
class Pair<T> {
private T value;
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
}
让一个子类去继承这个泛型类,在继承时父类泛型具体为Date。
class DateInter extends Pair<Date> {
@Override
public void setValue(Date value) {
super.setValue(value);
}
@Override
public Date getValue() {
return super.getValue();
}
}
这时候就有个问题,就是子类中的两个方法setValue和getValue是重写还是重载父类的方法?(冲突)
这里编译器给的重写override,但是在编译的时候,父类泛型被擦除了,由原来的Data变成了Object。子类泛型依旧是Data,方法中的参数数据类型改变了,这就不是重写而是重载。
可是由于种种原因,虚拟机并不能将泛型类型变为Date
,只能将类型擦除掉,变为原始类型Object
。这样,我们的本意是进行重写,实现多态。可是类型擦除后,只能变为了重载。这样,类型擦除就和多态有了冲突。JVM知道你的本意吗?知道!!!可是它能直接实现吗,不能!!!如果真的不能的话,那我们怎么去重写我们想要的Date
类型参数的方法啊。
于是JVM采用了一个特殊的方法,来完成这项功能,那就是桥方法。(暂时不知道)
6.通配符和上下界
通配符 ?:代表任意的数据类型(做程序不定下具体的数据类型)
使用方式:不能创建对象使用,只能作为方法的参数使用。(是实参非形参 =Integer 、Float),通常与上下界一起使用。
泛型上界限定:<? extend E> 代表使用泛型只能是E类的子类或者本身。最高只能是E类。
泛型下界限定:<? super E> 代表使用泛型只能是E类的父类或者本身。最低只能是E类。
参考:《JAVA编程思想 第4版》、几篇非常优秀的博客。