java基础/接口与继承1(0.96万字)

接口

接口就像是一种约定,我们约定某些英雄是物理系英雄,那么他们就一定能够进行物理攻击。

在设计LOL的时候,进攻类英雄有两种,一种是进行物理系攻击,一种是进行魔法系攻击
这时候,就可以使用接口来实现这个效果

创建接口

创建一个接口 File->New->Interface
AD ,声明一个方法 physicAttack 物理攻击,但是没有方法体,是一个“空”方法

package charactor;
 
public interface AD {
        //物理伤害
    public void physicAttack();
}

设计一类英雄,能够使用物理攻击
设计一类英雄,能够使用物理攻击,这类英雄在LOL中被叫做AD
类:ADHero
继承了Hero 类,所以继承了name,hp,armor等属性
实现某个接口,就相当于承诺了某种约定。所以,实现了AD这个接口,就必须提供AD接口中声明的方法physicAttack()
实现在语法上使用关键字 implements

package j2se;

public class ADHero extends Hero implements AD{
	
	//父类Hero只有有参构造方法,继承类就需要显式声明重写父类构造函数
	public ADHero(String name) {
		super(name);
		// TODO Auto-generated constructor stub
	}

	@Override
	public void physicAttack(){
		System.out.println("进行了物理攻击");
	}
}

魔法攻击接口

package charactor;
 
public interface AP {
    public void magicAttack();
}

设计一类英雄,只能使用魔法攻击
继承了Hero 类,所以继承了name,hp,armor等属性
同时,实现了AP这个接口,就必须提供AP接口中声明的方法magicAttack()。实现在语法上使用关键字 implement

package j2se;

public class APHero extends Hero implements AP {
	public APHero(String name){
		super(name);
	}
	@Override
	public void magicAttack(){
		System.out.println("进行了魔法攻击");
	}
}

设计一类英雄,既能进行物理攻击,又能进行魔法攻击

package j2se;

public class ADAPHero extends Hero implements AD,AP{
	public ADAPHero(String name){
		super(name);
	}
	@Override
	public void physicAttack(){
		System.out.println("进行了物理攻击");
	}
	
	@Override
	public void magicAttack(){
		System.out.println("进行了魔法攻击");
	}
}

使用接口

与类不同,接口不能new,不能直接创建一个借接口对象,对象只能通过类来创建。但是可以声明接口类型的变量,引用实现了接口的类对象

AD hero1 = new ADHero("hanbing");
System.out.println(hero1.physicAttack());

hero1是AD的类型变量,但是引用了ADHero类型的对象,之所以能赋值,是因为ADHero实现了AD接口,如果一个类型实现了多个接口,那么这种类型的对象就可以被赋值给任意接口类型的变量。
hero1可以调用AD接口的方法,也只能调用AD接口的方法(不能调用ADHero类中的其他实例或者类方法),实际执行时,执行的是具体实现类的代码。如下:

package lianxi;

public class ADHero implements AD{
    protected String name;
    public ADHero(String name) {
        this.name = name;
    }

    public static void test(){
        System.out.println("ADHero test");
    }

    @Override
    public void physicAttack(){
        System.out.println("进行了物理攻击");
    }

    public static void main(String[] args) {
        AD hero1 = new ADHero("hb");
        hero1.physicAttack();//执行ADHero中的physicAttack方法
        hero1.test();//错误
    }
}

使用接口的目的是:降低耦合,提高灵活性

什么样的情况下该使用接口?

如上的例子,似乎要接口,不要接口,都一样的,那么接口的意义是什么呢
学习一个知识点,是由浅入深得进行的。 这里呢,只是引入了接口的概念,要真正理解接口的好处,需要更多的实践,以及在较为复杂的系统中进行大量运用之后,才能够真正理解,比如在学习了多态之后就能进一步加深理解。

练习-接口
设计一个治疗者接口:Healer
该接口声明有方法: heal()
设计一个Support类,代表辅助英雄,继承Hero类,同时实现了Healer这个接口

package j2se;

public class Supper extends Hero implements Healer {
	public Supper(String name){
		super(name);
	}
	
	@Override
	public void heal(){
		System.out.println(this.name+"加了点血");
		
	}
	public static void main(String[] args) {
		Supper ad=new Supper("teemo");//实例化对象,调用构造方法
		ad.heal();
	}
}

对象转型

父类类型的引用可以调用父类中定义的所有属性和方法,而对于子类中定义而父类中没有的属性或方法,父类引用无法调用。子类重定义父类已有的属性,父类引用调用的还是本类的属性值。子类重定义父类已有的方法时,父类引用调用的是子类重写过的方法(这里只能调用实例方法,因为其实动态绑定的)。

明确引用类型与对象类型的概念

首先,明确引用类型与对象类型的概念
在这个例子里,有一个对象 new ADHero(), 同时也有一个引用ad
对象是有类型的, 是ADHero
引用也是有类型的,是ADHero
通常情况下,引用类型和对象类型是一样的
接下来要讨论的类型转换的问题,指的是引用类型和对象类型不一致的情况下的转换

package charactor;
 
public class Hero {
    public String name;
    protected float hp;    
    public static void main(String[] args) {    
        ADHero ad = new ADHero();
         
    }
}

所谓的转型,是指当引用类型和对象类型不一致的时候,才需要进行类型转换。类型转换有时候会成功,有时候会失败
判别到底能否转换成功?
把右边的当做左边来用,看能否说的通

子类转父类(向上转型)

Hero h = new Hero();
ADHero ad = new ADHero();
h = ad;

右边ad引用所指向的对象的类型是 物理攻击英雄
左边h引用的类型是 普通英雄
把物理攻击英雄 当做 普通英雄,说不说得通? 说得通,就可以转
所有的子类转换为父类,都是说得通的

父类转子类(向下转型)

总结:子类先指向父类,然后再把这个子类转子类就可以转
父类转子类,有的时候行,有的时候不行,所以必须进行强制转换。
强制转换的意思就是 转换有风险,风险自担。

什么时候可以?

1.        Hero h =new Hero();
2.        ADHero ad = new ADHero();
3.        h = ad;
4.        ad = (ADHero) h;
5. 'ad是ADHero类型的,也是Hero类型的'
6. "因为类型转换,所以h是ADHero类型的,也是Hero类型的。如果没有第三句 h就不是ADHero类型的"

第3行,是子类转父类,一定可以的
第4行,就是父类转子类,所以要进行强转
h这个引用,所指向的对象是ADHero, 所以第4行,就会把ADHero转换为ADHero,就能转换成功。
什么时候转换不行呢?

Base();//父类
Child();//子类
Base b = new Base();
Child c = Child(b);//这样是不行的

语法上java不会报错,但是运行时会抛出出错,错误为类型转换异常。
一个父类的变量能不能转换为一个子类的变量,取决于这个父类变量的动态类型(即引用的对象类型)是不是这个子类或者这个子类的

总结

在这里插入图片描述可以通过instanceof关键字判断给的一个父类变量能不能转换为一个子类的变量,从而安全地进行类型转换。

package charactor;
  
public class Hero {
    public String name;
    protected float hp;
      
    public boolean equals(Object o){//这里是传入Object类型或其子类型的对象(new name(),把o引用指向object对象或者子类对象)
        if(o instanceof Hero){
            Hero h = (Hero) o;
            return this.hp == h.hp;
        }
        return false;
    }
      
    public static void main(String[] args) {
        Hero h1= new Hero();
        h1.hp = 300;
        Hero h2= new Hero();
        h2.hp = 400;
        Hero h3= new Hero();
        h3.hp = 300;
         
        System.out.println(h1.equals(h2));
        System.out.println(h1.equals(h3));
    }
}
    public boolean equals(Object o){//这里是传入Object类型或其子类型的对象(new name(),把o引用指向object对象或者子类对象)
        if(o instanceof Hero){
            Hero h = (Hero) o;
            return this.hp == h.hp;
        }
        return false;
    }

其中:

  • public boolean equals(Object o)这里是传入Object类型或其子类型的对象(new name(),把o引用指向object对象或者子类对象),object o = new name(),子类转父类一定可以 。设置为object,就不会出现传入错误的情况。如果是(ADHero o),则传入new Hero()就会出错
  • if(o instanceof Hero)判断传入的对象是不是Hero类型,或其子类型的
  • Hero h = (Hero) o; (Hero) o是强制 父类转子类,其之所以成功。是应为object的引用o指向了传入的对象new name(),而传入的对象通过上一步的判断,其是Hero类或Hero类的子类
  • return this.hp == h.hp;,h.hp是访问的传入对象的hp,父类引用指向子类对象,引用访问方法时,如果子类有重写方法,则访问子类的方法。但是不能访问子类中新加的属性和方法,也就是说在父类中没有定义的属性和方法。

没有继承关系的两个类,互相转换,一定会失败

虽然ADHeroAPHero都继承了Hero,但是彼此没有互相继承关系
比如:“把魔法英雄当做物理英雄来用”,在语义上也是说不通的

实现类转换成接口(向上转型)

总结:也就是说有实现接口方法的类类型对象才能转换为接口,接口直接转换为类类型会失败,需要先转换
引用ad指向的对象是ADHero类型,这个类型实现了AD接口
10行: 把一个ADHero类型转换为AD接口
从语义上来讲,把一个ADHero当做AD来使用,而AD接口只有一个physicAttack方法,这就意味着转换后就有可能要调用physicAttack方法,而ADHero一定是有physicAttack方法的,所以转换是能成功的。(转换为AP接口就不行)

package charactor;
   
public class Hero {
    public String name;
    protected float hp;
       
    public static void main(String[] args) {
        ADHero ad = new ADHero();
          
        AD adi = ad;         
    }       
}

接口转换成实现类(向下转型)

10行: ad引用指向ADHero, 而adi引用是接口类型:AD,实现类转换为接口,是向上转型,所以无需强制转换,并且一定能成功
12行: adi实际上是指向一个ADHero的,所以能够转换成功
14行: adi引用所指向的对象是一个ADHero,要转换为ADAPHero就会失败。(假设能够转换成功,那么就可以使用magicAttack方法,而adi引用所指向的对象ADHero是没有magicAttack方法的。)
在这里插入图片描述

instanceof

instanceof Hero 判断一个引用所指向的对象,是否是Hero类型,或者Hero的子类

System.out.println(h1 instanceof ADHero);
Hero h = ad 这一行是没错 是由子转父,
AD adi = (AD) h; 这一行是因为h指向ADHero,强行转为AD也是可行的,最后达到父转子
APHero ap = (APHero) adi; 这个adi是指向AD的,不能强行转向APhero,所以这里有问题

重写

子类可以继承父类的对象方法
在继承后,重复提供该方法,就叫做方法的重写
又叫覆盖 override
比如:父类Item有个方法

package j2se;

public class Item {
	
	String name;
	int price;
	
	public void buy(){
		System.out.println("购买");
	}
	public void effect(){
		System.out.println("物品使用后,可以有效果");
	}
	
	public static void main(String[] args) {
		Item bloodBottle = new Item();
		bloodBottle.name = "血瓶";
		bloodBottle.price = 50;
		
		Item shoots = new Item();
		shoots.name = "草鞋";
		shoots.price = 300;
	
		Item sword = new Item();
		sword.name = "长剑";
		sword.price = 350;
		
		bloodBottle.effect();
		'子类调用effect方法,就会执行重写的方法,而不是从父类的方法'
		LifePotion lp = new LifePotion();
		lp.effect();
	}
}
package j2se;

public class LifePotion extends Item{
	'重写父类的effect方法'
	public void effect(){
		System.out.println("血瓶使用后,可以回血");
	}
}

如果没有重写这样的机制,也就是说LifePotion这个类,一旦继承了Item,所有方法都不能修改了。
但是LifePotion又希望提供一点不同的功能,为了达到这个目的,只能放弃继承Item,重新编写所有的属性和方法,然后在编写effect的时候,做一点小改动.
这样就增加了开发时间和维护成本

多态

操作符的多态
+ 可以作为算数运算,也可以作为字符串连接
类的多态
父类引用指向子类对象

操作符的多态

同一个操作符在不同情境下,具备不同的作用
如果+号两侧都是整型,那么+代表 数字相加
如果+号两侧,任意一个是字符串,那么+代表字符串连接

类的多态

观察类的多态现象

观察类的多态现象:

  1. i1i2都是Item类型
  2. 都调用effect方法
  3. 输出不同的结果
    多态: 都是同一个类型,调用同一个方法,却能呈现不同的状态
package property;
 
public class Item {
    String name;
    int price;
 
    public void buy(){
        System.out.println("购买");
    }
    public void effect() {
        System.out.println("物品使用后,可以有效果 ");
    }
     
    public static void main(String[] args) {
        Item i1= new LifePotion();
        Item i2 = new MagicPotion();
        System.out.print("i1  是Item类型,执行effect打印:");
        i1.effect();
        System.out.print("i2也是Item类型,执行effect打印:");
        i2.effect();
    }
 
}
类的多态条件以及作用

要实现类的多态,需要如下条件

  1. 父类(接口)引用指向子类对象
  2. 调用的方法有重写

那么多态有什么作用是什么?
不使用多态时:
如果不使用多态,
假设英雄要使用血瓶和魔瓶,就需要为Hero设计两个方法
useLifePotionuseMagicPotion
除了血瓶和魔瓶还有很多种物品,那么就需要设计很多很多个方法,比如
usePurityPotion 净化药水
useGuard 守卫
useInvisiblePotion 使用隐形药水
等等等等

package charactor;
 
import property.LifePotion;
import property.MagicPotion;
   
public class Hero {
    public String name;
    protected float hp;
 
    public void useLifePotion(LifePotion lp){
        lp.effect();
    }
    public void useMagicPotion(MagicPotion mp){
        mp.effect();
    }
 
    public static void main(String[] args) {
         
        Hero garen =  new Hero();
        garen.name = "盖伦";
     
        LifePotion lp =new LifePotion();
        MagicPotion mp =new MagicPotion();
         
        garen.useLifePotion(lp);
        garen.useMagicPotion(mp);
         
    }
       
}

使用多态
如果物品的种类特别多,那么就需要设计很多的方法
比如useArmoruseWeapon等等
这个时候采用多态来解决这个问题
设计一个方法叫做useItem,其参数类型Item(是为了让父引用指向子类对象)
如果是使用血瓶,调用该方法
如果是使用魔瓶,还是调用该方法
无论英雄要使用什么样的物品,只需要一个方法即可

package charactor;
 
import property.Item;
import property.LifePotion;
import property.MagicPotion;
   
public class Hero {
    public String name;
    protected float hp;
 
    public void useItem(Item i){
        i.effect();
    }
 
    public static void main(String[] args) {
         
        Hero garen =  new Hero();
        garen.name = "盖伦";
     
        LifePotion lp =new LifePotion();
        MagicPotion mp =new MagicPotion();        
        garen.useItem(lp);'这里有一个子类型向父类转换Item i = lp'
        garen.useItem(mp);             
    }    
}

练习:
immortal是不朽的,不死的意思
mortal就是终有一死的,凡人的意思

  1. 设计一个接口
    接口叫做Mortal,其中有一个方法叫做die
  2. 实现接口
    分别让ADHero,APHero,ADAPHero这三个类,实现Mortal接口,不同的类实现die方法的时候,都打印出不一样的字符串
  3. 为Hero类,添加一个方法,在这个方法中调用 m的die方法。
public void kill(Mortal m)
  1. 在主方法中
    首先实例化出一个Hero对象:盖伦
    然后实例化出3个对象,分别是ADHero,APHero,ADAPHero的实例
    然后让盖伦 kill 这3个对象
public class Hero {
    
    String name; //姓名
        
    float hp; //血量
        
    float armor; //护甲
        
    int moveSpeed; //移动速度
        
    //带一个参数的构造方法
    public Hero(String name){
        System.out.println("创建"+ name);
        this.name = name;
    }
    public void kill(mortal m){
    	m.die();
    }
      
    //带两个参数的构造方法
/*   public Hero(String name,float hp){
        this(name);
        System.out.println("两个参数的构造方法");
        this.hp = hp;
    }*/
 
    public static void main(String[] args) {
//        Hero teemo =  new Hero("提莫",383);
    	Hero teemo =  new Hero("提莫");
        ADHero hanbing = new ADHero("寒冰");
        APHero Karsatin = new APHero("卡萨丁");
        ADAPHero EZ = new ADAPHero("EZ");
 //     System.out.println(teemo.name);
        teemo.kill(hanbing);
        teemo.kill(Karsatin);
        teemo.kill(EZ);
    }
package j2se;

public class ADHero extends Hero implements AD,mortal{
	
	public ADHero(String name) {
		super(name);
		// TODO Auto-generated constructor stub
	}

	@Override
	public void physicAttack(){
		System.out.println("进行了物理攻击");
	}
	@Override
	public void die(){
		System.out.println(this.name +"被杀死了");
	}
	
}

多态和继承的区别

看上例中继承和多态的掺杂,就弄混了。
继承是子类使用父类的方法和属性(除了private),而多态是,通过子类重写父类的方法,在父类中通过父类引用指向子类实例调用子类的方法。

继承可以扩展已存在的代码模块(类);它们的目的都是为了——代码重用。而多态则是为了实现另一个目的——接口重用!多态的作用,就是为了类在继承和派生的时候,保证使用“家谱”中任一类的实例的某一属性时的正确调用。

多态与继承、方法重写密切相关,我们在方法中接收父类类型作为参数,在方法实现中调用父类类型的各种方法。当把子类作为参数传递给这个方法时,java虚拟机会根据实际创建的对象类型,调用子类中相应的方法(存在方法重写时)。
多态性是指允许不同类bai的对象对同一消息作出响应。多态性du包括参数化多态性和包含zhi多态性。多态性语言具有灵活、dao抽象、行为共享、代码共享的优势,很好的解决了应用程序函数同名问题。
多态的表现形式有重载和覆盖两种形式。
重载(overload),是发生在同一类中。与什么父类子类、继承毫无关系。
标识一个函数除了函数名外,还有函数的参数(个数和类型)。也就是说,一个类中可以有两个或更多的函数,叫同一个名字而他们的参数不同。
他们之间毫无关系,是不同的函数,只是可能他们的功能类似,所以才命名一样,增加可读性,仅此而已!
再说覆盖(override),是发生在子类中!也就是说必须有继承的情况下才有覆盖发生。
我们知道继承一个类,也就有了父类了全部方法,如果你感到哪个方法不爽,功能要变,那就把那个函数在子类中重新实现一遍。
这样再调用这个方法的时候,就是执行子类中的过程了。父类中的函数就被覆盖了。(当然,覆盖的时候函数名和参数要和父类中完全一样,不然你的方法对父类中的方法就不起任何作用,因为两者是两个函数,毫不关系)
在java中
高内聚,低耦合。
继承就可以降低代码的耦合,这样编写代码,在你以后修改一处时,就不会牵扯很多的累,方便以后的修改和升级。

隐藏

与重写类似,方法的重写是子类覆盖父类的对象方法

隐藏,就是子类覆盖父类的类方法
父类有一个类方法 :battleWin

package charactor;
  
public class Hero {
    public String name;
    protected float hp;
  
    //类方法,静态方法
    //通过类就可以直接调用
    public static void battleWin(){
        System.out.println("hero battle win");
    }
      
}

子类隐藏父类的类方法

package charactor;
  
public class ADHero extends Hero implements AD{
  
    @Override
    public void physicAttack() {
        System.out.println("进行物理攻击");
    }
     
    //隐藏父类的battleWin方法
    public static void battleWin(){
        System.out.println("ad hero battle win");
    }   
     
    public static void main(String[] args) {
        Hero.battleWin();
        ADHero.battleWin();
    }
  
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
这个错误是由于在客户端代码中的线程,Scanner对象被关闭导致的。为了解决这个问题,你可以将Scanner对象的创建和关闭操作移到主线程中。下面是修改后的代码: ```java import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.Socket; import java.util.Scanner; public class Client { private static final String SERVER_IP = "localhost"; private static final int SERVER_PORT = 8888; private String username; public void start() { try { Socket socket = new Socket(SERVER_IP, SERVER_PORT); System.out.println("成功连接服务器"); InputStream inputStream = socket.getInputStream(); OutputStream outputStream = socket.getOutputStream(); // 发送消息给服务器 new Thread(() -> { try { Scanner scanner = new Scanner(System.in); while (true) { String message = scanner.nextLine(); outputStream.write(message.getBytes()); outputStream.flush(); } } catch (IOException e) { e.printStackTrace(); } }).start(); // 接收服务器发送的消息 try { byte[] buffer = new byte[1024]; int len; while ((len = inputStream.read(buffer)) > 0) { String message = new String(buffer, 0, len); System.out.println("收到服务器消息:" + message); } } catch (IOException e) { e.printStackTrace(); } finally { socket.close(); } } catch (IOException e) { e.printStackTrace(); } } public static void main(String[] args) { Client client = new Client(); client.start(); } } ``` 在修改后的代码中,我们将接收服务器发送消息的逻辑移到了主线程中,并在最后关闭了Socket连接。这样就避免了在子线程中关闭Scanner对象和Socket连接的问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值