看看下面这个例子,就会明白了:JAVA中继承可以实现代码复用,
成都创新互联公司专注为客户提供全方位的互联网综合服务,包含不限于网站设计制作、成都网站制作、汕尾网络推广、小程序设计、汕尾网络营销、汕尾企业策划、汕尾品牌公关、搜索引擎seo、人物专访、企业宣传片、企业代运营等,从售前售中售后,我们都将竭诚为您服务,您的肯定,是我们最大的嘉奖;成都创新互联公司为所有大学生创业者提供汕尾建站搭建服务,24小时服务热线:18982081108,官方网址:www.cdcxhl.com
由于在父类中已经定义的方法,被子类继承以后,就可以使用,实现了代码的复用
class Father{
private int moneyDollar=300;
int moneyHK=200;
int add(int x,int y){
return x+y;
}
}
class Son extends Father{
int moneyRMB=800;
public void changMoneyHK(int x){
moneyHK=x;
}
public void changMoneyRMB(int x){
moneyRMB=x;
}
int subs(int x,int y){
return x-y;
}
}
class GrandSon extends Son{
int multi(int x,int y){
return x*y;
}
}
public class Example5_1{
public static void main(String args[]){
int a=5,b=3;
Son son=new Son();
GrandSon sunzi=new GrandSon();
son.changMoneyHK(666);
son.changMoneyRMB(5000);
System.out.println("儿子的港币是继承的属性,当前的值是:"+son.moneyHK);
System.out.println("儿子的人民币是新增的属性,当前的值是:"+son.moneyRMB);
System.out.printf("减法是儿子新增的功能,%d-%d等于%d\n",a,b,son.subs(a,b));
System.out.printf("加法是儿子继承的功能,%d+%d等于%d\n",a,b,son.add(a,b));
System.out.println("孙子的港币和人民币都是继承的属性,,当前的值是:");
System.out.println("港币:"+sunzi.moneyHK+" 人民币:"+sunzi.moneyRMB);
System.out.printf("乘法是孙子新增的功能,%d*%d等于%d\n",a,b,sunzi.multi(a,b));
System.out.printf("加法是孙子继承的功能,%d+%d等于%d\n",a,b,sunzi.add(a,b));
System.out.printf("减法是孙子继承的功能,%d-%d等于%d\n",a,b,sunzi.subs(a,b));
}
}
1、函数式接口
Java 8 引入的一个核心概念是函数式接口(Functional Interfaces)。通过在接口里面添加一个抽象方法,这些方法可以直接从接口中运行。如果一个接口定义个唯一一个抽象方法,那么这个接口就成为函数式接口。同时,引入了一个新的注解:@FunctionalInterface。可以把他它放在一个接口前,表示这个接口是一个函数式接口。这个注解是非必须的,只要接口只包含一个方法的接口,虚拟机会自动判断,不过最好在接口上使用注解 @FunctionalInterface 进行声明。在接口中添加了 @FunctionalInterface 的接口,只允许有一个抽象方法,否则编译器也会报错。
java.lang.Runnable 就是一个函数式接口。
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
2、Lambda 表达式
函数式接口的重要属性是:我们能够使用 Lambda 实例化它们,Lambda 表达式让你能够将函数作为方法参数,或者将代码作为数据对待。Lambda 表达式的引入给开发者带来了不少优点:在 Java 8 之前,匿名内部类,监听器和事件处理器的使用都显得很冗长,代码可读性很差,Lambda 表达式的应用则使代码变得更加紧凑,可读性增强;Lambda 表达式使并行操作大集合变得很方便,可以充分发挥多核 CPU 的优势,更易于为多核处理器编写代码;
Lambda 表达式由三个部分组成:第一部分为一个括号内用逗号分隔的形式参数,参数是函数式接口里面方法的参数;第二部分为一个箭头符号:-;第三部分为方法体,可以是表达式和代码块。语法如下:
1. 方法体为表达式,该表达式的值作为返回值返回。
(parameters) - expression
2. 方法体为代码块,必须用 {} 来包裹起来,且需要一个 return 返回值,但若函数式接口里面方法返回值是 void,则无需返回值。
(parameters) - { statements; }
例如,下面是使用匿名内部类和 Lambda 表达式的代码比较。
下面是用匿名内部类的代码:
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.print("Helllo Lambda in actionPerformed");
}
});
下面是使用 Lambda 表达式后:
button.addActionListener(
\\actionPerformed 有一个参数 e 传入,所以用 (ActionEvent e)
(ActionEvent e)-
System.out.print("Helllo Lambda in actionPerformed")
);
上面是方法体包含了参数传入 (ActionEvent e),如果没有参数则只需 ( ),例如 Thread 中的 run 方法就没有参数传入,当它使用 Lambda 表达式后:
Thread t = new Thread(
\\run 没有参数传入,所以用 (), 后面用 {} 包起方法体
() - {
System.out.println("Hello from a thread in run");
}
);
通过上面两个代码的比较可以发现使用 Lambda 表达式可以简化代码,并提高代码的可读性。
为了进一步简化 Lambda 表达式,可以使用方法引用。例如,下面三种分别是使用内部类,使用 Lambda 表示式和使用方法引用方式的比较:
//1. 使用内部类
FunctionInteger, String f = new FunctionInteger,String(){
@Override
public String apply(Integer t) {
return null;
}
};
//2. 使用 Lambda 表达式
FunctionInteger, String f2 = (t)-String.valueOf(t);
//3. 使用方法引用的方式
FunctionInteger, String f1 = String::valueOf;
要使用 Lambda 表达式,需要定义一个函数式接口,这样往往会让程序充斥着过量的仅为 Lambda 表达式服务的函数式接口。为了减少这样过量的函数式接口,Java 8 在 java.util.function 中增加了不少新的函数式通用接口。例如:
FunctionT, R:将 T 作为输入,返回 R 作为输出,他还包含了和其他函数组合的默认方法。
PredicateT :将 T 作为输入,返回一个布尔值作为输出,该接口包含多种默认方法来将 Predicate 组合成其他复杂的逻辑(与、或、非)。
ConsumerT :将 T 作为输入,不返回任何内容,表示在单个参数上的操作。
例如,People 类中有一个方法 getMaleList 需要获取男性的列表,这里需要定义一个函数式接口 PersonInterface:
interface PersonInterface {
public boolean test(Person person);
}
public class People {
private ListPerson persons= new ArrayListPerson();
public ListPerson getMaleList(PersonInterface filter) {
ListPerson res = new ArrayListPerson();
persons.forEach(
(Person person) -
{
if (filter.test(person)) {//调用 PersonInterface 的方法
res.add(person);
}
}
);
return res;
}
}
为了去除 PersonInterface 这个函数式接口,可以用通用函数式接口 Predicate 替代如下:
class People{
private ListPerson persons= new ArrayListPerson();
public ListPerson getMaleList(PredicatePerson predicate) {
ListPerson res = new ArrayListPerson();
persons.forEach(
person - {
if (predicate.test(person)) {//调用 Predicate 的抽象方法 test
res.add(person);
}
});
return res;
}
}
3、接口的增强
Java 8 对接口做了进一步的增强。在接口中可以添加使用 default 关键字修饰的非抽象方法。还可以在接口中定义静态方法。如今,接口看上去与抽象类的功能越来越类似了。
默认方法
Java 8 还允许我们给接口添加一个非抽象的方法实现,只需要使用 default 关键字即可,这个特征又叫做扩展方法。在实现该接口时,该默认扩展方法在子类上可以直接使用,它的使用方式类似于抽象类中非抽象成员方法。但扩展方法不能够重载 Object 中的方法。例如:toString、equals、 hashCode 不能在接口中被重载。
例如,下面接口中定义了一个默认方法 count(),该方法可以在子类中直接使用。
public interface DefaultFunInterface {
//定义默认方法 countdefault int count(){
return 1;
}
}
public class SubDefaultFunClass implements DefaultFunInterface {
public static void main(String[] args){
//实例化一个子类对象,改子类对象可以直接调用父接口中的默认方法 count
SubDefaultFunClass sub = new SubDefaultFunClass();
sub.count();
}
}
静态方法
在接口中,还允许定义静态的方法。接口中的静态方法可以直接用接口来调用。
例如,下面接口中定义了一个静态方法 find,该方法可以直接用 StaticFunInterface .find() 来调用。
public interface StaticFunInterface {public static int find(){
return 1;
}
}
public class TestStaticFun {
public static void main(String[] args){
//接口中定义了静态方法 find 直接被调用
StaticFunInterface.fine();
}
}
如果你用的是eclipse可以用快捷键来抽取方法的,就是选中重复的代码,然后按alt+shift+m,就解决你的代码重复问题。
-是Java 8新增的Lambda表达式中,变量和临时代码块的分隔符,即:
(变量) - {代码块}
如果代码块只有一个表达式,大括号可以省略。如果变量类型可以自动推断出来,可以不写变量类型。
::是类似于C++的域运算符,获取方法使用的。
stream()也是JDK8新增的流,你的表达式中将numbers转换为流,就可以惰性处理,这样只有变量要用的时候才会被调用,专门处理较多的数字或者字符串(如配合readAllLines()方法使用),i - i % 2 == 0就是自动判断了i的类型(可能是int型,我没法判断),这样就将numbers中所有的偶数过滤出来了(distinct()保证了每个数字只保留一个),然后将这些过滤出的数字每个占用一行地打印出来。
代码复用能够大大简化我们的工作。面向对象的语言中一般是通过对类的重复使用来达到代码复用的目的的,Java也不例外。在Java中,复用类有两种方式,合成(has-a)与继承(is-a)。合成就是在新的类中直接创建旧类的对象,这里我们复用的只是代码的功能而不是它的形式。而继承是在原有的类的基础上建立一个新类,新类具有旧类的形式,但也加入了一些新的特性。这一章介绍的主要就是合成与继承方面的知识。
一、合成所使用的语法
合成的语法很简单,只要把要复用的类的对象的引用直接放到新类里就可以了。当然仅仅这样还是不够的,我们还要创建这个类的对象让那个引用来指向它。因为Java不会帮我们自动创建一个缺省的对象,它只会自动替我们把字段中的引用初始化为null。为引用赋值可以在三个地方,一个就是在定义这个引用的时候,另一个是在构造函数中,第三个地方就是在即将要使用这个对象之前。为了防止忘记在使用前为引用赋值,我们一般应该在前两种场合来创建对象。如果我们要创建的这个对象会花费很大开销,而且又可能不是每次都需要创建它的话,我们可以考虑第三种方式来创建这个对象。
二、继承所使用的语法
继承是Java中的重要部分,因为Java是使用单根体系的(C++不是这样,因为它要保持向C的兼容),所以我们定义的每一个类都是继承自Java中的根类Object类。在定义一个继承自已有的类的类时,要使用extends关键字,其后跟上基类的名字,这样表示新定义的这个类是继承自那个基类。在Java中不允许多重继承(C++中允许),也就是说它不允许一个类拥有多于一个的基类,这点劣势可以用接口来弥补,因为Java允许一个类实现任意多个接口。
一个子类会自动获得基类中的全部字段与方法(那些由访问控制符控制的对子类而言不可见的成员也会获得,只是不可见,用不了),这也就是对基类中代码的复用。除了自动获得自基类的代码外,子类中还可定义新的成员,也可以覆写基类中的方法(所谓覆写指的是方法的声明部分一样但实现不一样),这样可以让相同签名的方法拥有不一样的形为。
因为子类自动拥有了基类的成员,因此在子类中自然就可以调用基类的方法。如果这个方法在子类中被覆写过,那编译器知道你是要调用哪个方法呢?Java提供了super关键字在类中表示该类的基类的引用,我们可以通过这个关键字来明确表示我们要用到的是基类中的成员。如果不写super的话,那编译器将会理解为嵌套调用。
这里有个题外话。在Java程序中常常是用public类中的main()方法做为整个程序的入口。这样的静态main()方法并不是非得要在public类中才能出现的,静态的main()方法可以做所有类的入口(但只能是main(),而不能是其它名字的什么静态方法)。比如一个程序有多个class组成,我们要对其中的某个class进行单元测试时,我们就可以在这个class文件中加入main(),编译后生成这个类的.class文件,在控制台通过java来运行它就是了。
子类继承了一个基类后便拥有了基类中的成员,也就可以通过创建的子类对象来访问基类中可见的成员。Java是怎样做到这一点的呢?在我们创建一个子类对象的时候,这里创建的已经不是一个类的对象了,它还会创建这个类的基类的对象,这个基类的对象创建后被包括在子类的对象中。也就是说创建的子类的对象拥有其基类全部的成员(从这就可以知道为什么可以上传),但是子类对象只能访问基类中它可见的成员。那么在创建一个这样的对象时,子类和基类对象创建的顺序是怎么样的呢?为了能够正确的初始化基类,一般会调用基类的构造函数来进行初始化。Java中在调用子类的构造函数时首先会自动的调用基类的构造函数,并且这样的过程是层层传递的。比如C继承了B,而B又继承了A,在创建C的对象时,C的构造函数会首先调用B的构造函数,这时B的构造函数又会首先调用A的构造函数。(如果基类中没有默认构造函数,编译时就会报错。)但是这里自动调用的都是基类的默认构造函数(无参的),如果我们想调用基类的某个带参数的构造函数又该怎么办呢?上面提到可以用super来代替基类的引用,与在构造函数中通过this调用本类其它构造函数的形式一样,我们可以通过super来调用基类带参数的构造函数,比如“super(i, j)”。与调用本类的其它构造函数一样,对基类构造函数的显示调用也需要放在子类构造函数的最前面,在它之前不能有任何东西,如果基类的构造函数会抛出异常需要捕获的话,就会比较麻烦。
1. 局部代码块
作用在方法当中,作用是控制变量的生命周期:
public void show(){
{
System.out.println("局部代码块运行!");
}
}123456
在程序中当我们定义完成一个局部变量x之后,并且在接下来的代码中,不想再用到它时,那么就没必要让x在内存中继续占用空间。因此就有了局部代码块。
2. 构造代码块
作用在类的定义Body中,作用是给类的部分字段统一初始化:
public class Apple {
private String size;
//构造代码块
{
System.out.println("构造代码块运行!");
size = "E";
}
}
12345678910
构造代码块与构造函数的区别是:构造代码块是给所有对象进行统一初始化,而构造函数是给对应的对象初始化,因为构造函数是可以多个的,运行哪个构造函数就会建立什么样的对象,但无论建立哪个对象,都会先执行相同的构造代码块。也就是说,构造代码块中定义的是不同对象共性的初始化内容。所以理所当然的,构造代码块在构造函数之前执行。
3. 静态代码块
作用有两个:
(1)给类的静态变量赋值;
(2)声明静态变量;
作用在类的Body中,对类中的静态变量初始化:
public class APP {
static int x, y; // 静态变量
static {
x = 5; // 给静态变量x赋值
}
public static void myMethod() {
y = x++ + ++x; // x++ 先使用x的值再加1;++x先加1再使用x的值
}
public static void main(String[] args) {
x--;
myMethod();
System.out.println(x + y + ++x);
}
}
输出:23