Java学习笔记

面向对象(2)

Java8 增强的包装类

  • Java的8种基本数据类型不具备对象的特性。为解决基本数据类型变量不能当成Object类型变量使用的问题,Java提供了包装类的概念,为8种基本数据类型分别定义了相应的引用类型,并称之为基本数据类型的包装类。
基本数据类型包装类
byteByte
shortShort
intInteger
longLong
charCharacter
floatFloat
doubleDouble
booleanBoolean
  • Java提供的基本类型变量和包装类对象之间的转换有点繁琐,JDK1.5 提供了自动装箱(Autoboxing)和自动拆箱(AutoUnboxing)功能。所谓自动装箱,就是可以把一个基本类型变量直接赋给对应的包装类变量,或者赋给Object变量(Object是所有类的父类,子类对象可以直接赋给父类变量);自动拆箱则与之相反,允许直接把包装类对象直接赋给一个对应的基本类型变量。
public class AutoBoxingUnboxing {public static void main(String[] args){//直接把一个基本类型变量赋给Integer对象Integer inObj = 5;Object boolObj = true;int it = inObj;if(boolObj instanceof Boolean){boolean b = (Boolean)boolObj;System.out.println(b);}}
}
  • 当JDK提供了自动装箱与自动拆箱功能够,大大简化了基本类型变量和包装类对象之间的转化过程。值得指出的是,进行自动装箱和自动拆箱时必须注意类型匹配,例如Integer只能自动拆箱成int类型变量,不要试图拆箱成boolean类型变量;与之类似的是,int类型变量只能自动装箱成Integer对象(即使赋给Object类型变量,那也只是利用了Java的自动向上转型特性),不要试图装箱成Boolean类型对象。
  • 包装类提供了两种方式,把字符串类型的值转化为基本类型的值
    • parseXxx(String s)静态方法
    • valueOf(String s)静态方法
public class PrimitivetoString {public static void main(String[] args){String intStr = "123";int it1 = Integer.parseInt(intStr);int it2 = Integer.valueOf(intStr);System.out.println(it2);String floatStr = "4.56";float ft1 = Float.parseFloat(floatStr);float ft2 = Float.valueOf(floatStr);System.out.println(ft2);String dbStr = String.valueOf(3.344);System.out.println(dbStr);String boolstr = String.valueOf(true);System.out.println(boolstr.toUpperCase());}
}
  • 如果希望把基本类型变量转换为字符串,还有一种更简单的方法:将基本类型变量和""进行连接运算,系统会自动把基本类型变量转换为字符串。
// intStr 的值为"5"
String intStr = 5+"";
  • 虽然包装类型的变量是引用数据类型,但包装类的实例可以与数值类型的值进行比较,这种比较是直接取出包装类实例所包装的数值来进行比较的。
Integer a = Integer.valueOf(6);
System.out.println("6的包装类实例是否大于5.0"+(a>5.0));
  • java8 中再次增强了包装类的功能。其中一个重要的增强就是支持无符号算数运算,为Integer,Long增加了如下方法:
static String toUnsignedString(int/long i):该方法用于将指定的整数转换为无符号字符串
static String toUnsignedString(int/long i, int radix):再额外指定进制
static xxx parseUnsignedXxx(String s):将指定字符串解析成无符号整数,xxx代表调用类的基本类型
static xxx parseUnsignedXxx(String s, int radix)
static int compareUnsigned(xxx x, xxx y):该方法将x、y两个整数转换为无符号整数比较大小
static xxx divideUnsigned(xxx dividend, xxx divisor):该方法将x、y两个整数转换为无符号整数后计算他们相除的商
static xxx remainderUnsigned(xxx dividend, xxx divisor):计算余数

处理对象

  • Java对象都是Object类的实例,都可以直接调用该类中定义的方法,这些方法提供了处理Java对象的通用方法

打印对象和toString方法

package PrintObject;
class Person
{private String name;public Person(String name){this.name = name;}
}public class PrintObject {public static void main(String[] args){Person p = new Person("lancibe");System.out.println(p);}
}
  • 运行看到如下结果:
PrintObject.Person@6ff3c5b5
  • @符号后面的8位十六进制数字可能发生改变。println方法只能在控制台输出字符串,而Person实例是一个内存中的对象,怎么能直接转换为字符串输出呢。当时用该方法输出Person实例其实输出的是Person对象的toString()方法的返回值。也就是说,下面代码效果完全一样。
System.out.println(p);
System.out.println(p.toString());
  • toString()方法是Object类里的一个实例方法,所有的Java类都是Object类的子类,所以所有对象都具有toString()方法。不仅如此,所有的Java对象都可以和字符串进行连接运算,实际上也是调用了该实例的toString()方法的返回值与字符串进行连接运算。
  • 这个方法是一个特殊的方法,他是一个“自我描述”的方法,该“方法”通常用于实现这样的功能:当程序员直接打印该对象时,系统将会输出该对象的“自我描述信息”用以告诉外界该对象的状态信息。他的输出结果是类名+@+hashCode值,但他不能真正实现自我描述功能,因此如果用户需要自定义类能实现该功能,则需要重写该方法。

== 和 equals 方法

  • java程序中测试两个变量是否相等有两种方式:一种是利用 == 运算符,另一种是利用 equals() 方法。当使用==时,如果两个变量都是基本类型且都是数值类型(注意不一定要求数据类型严格相同),则只要两个变量的值相等,就将返回true。
  • 但对于引用类型的变量,只有当他们指向同一个对象时,==判断才会返回true。==不可用于比较类型上没有父子关系的两个对象。
public class EqualTest {public static void main(String[] args){int it = 65;float fl = 65.0f;System.out.println("65和65.0f是否相等?"+(it==fl));char ch = 'A';System.out.println("65和A是否相等?"+(it==ch));String str1 = new String("hello");String str2 = new String("hello");System.out.println("str1和str2是否相等?"+(str1 == str2));System.out.println("str1是否equals str2?"+(str1.equals(str2)));//由于java.lang.String与EqualTest类没有继承关系,所以下面语句导致编译错误//System.out.println("hello" == new EqualTest());}
}
  • 对于str1和str2,因为他们都是引用类型变量,他们分别指向两个通过new关键字创建的String对象,因此str1和str2并不相等。
  • 当Java程序直接使用型如"hello"的字符串直接量,JVM将会使用常量池来管理这些字符串;当使用new String("hello");时,JVM会先使用常量池里的"hello"直接量,再调用String类的构造器来创建一个新的String类对象,新创建的String对象被保存在堆内存中。换句话说,new String("hello");一共产生了两个字符串对象。
public class StringCompareTest {public static void main(String[] args){//s1直接引用常量池中的"lancibe"String s1 = "lancibe";String s2 = "lan";String s3 = "cibe";//s4后面的字符串值可以在编译时就确定下来//s4直接引用常量池的"lancibe"String s4 = "lan"+"cibe";//s5后面的字符串值可以在编译时就确定下来//s5直接引用常量池中的"lancibe"String s5 = "l"+"an"+"cibe";//s6后面的字符串值不能在编译时就确定下来//不能引用常量池中的字符串String s6 = s2+s3;//使用new调用构造器将会创建一个新的String对象//s7应用堆内存中新创建的String对象String s7 = new String("lancibe");System.out.println(s1==s4);//trueSystem.out.println(s1==s5);//trueSystem.out.println(s1==s6);//falseSystem.out.println(s1==s7);//false}
}
  • JVM常量池保证相同的字符串直接量只有一个,不会产生多个副本。
  • 使用new String();创建的字符串对象是运行时创建出来的,他被保存在运行时内存区内,不会放入常量池中。
  • 但在很多时候,程序判断两个引用变量是否相等时,也希望有一种类似于“值相等”的判断,并不严格要求两个引用变量指向同一个对象。此时就可以利用String对象的equals()方法,如上面的程序中的str1.equals(str2);将返回true。
  • 该方法是Object类提供的一个实例方法,因此所有的引用变量都可以调用该方法来判断是否与其他引用变量相等。但使用这个方法判断两个对象相等的标准与使用==运算符没有区别,同样要求引用变量指向同一个对象才返回true。因此这个Object类提供的equals方法没有太大的实际意义,如果希望采用自定义的相等标准,则可采用重写该方法来实现。
package OverrideEqualsError;
class Person
{public boolean equals(Object obj){return true;}
}
class Dog{}
public class OverrideEqualsError {public static void main(String[] args){Person p = new Person();System.out.println("Person对象是否equals Dog对象?"+p.equals(new Dog()));System.out.println("Person对象是否equals String对象?"+p.equals(new String("hello")));}
}
  • 造成程序的荒诞结果的原因是由于重写Person类的equals()方法时没有任何判断,无条件的返回true。
package OverrideEqualsRight;
class Person
{private String name;private String idStr;public Person(){}public Person(String name, String idStr){this.name = name;this.idStr = idStr;}//Java推荐使用的getter和setter方法public void setName(String name) {this.name = name;}public void setIdStr(String idStr) {this.idStr = idStr;}public String getName() {return name;}public String getIdStr() {return idStr;}public boolean equals(Object obj){//如果两个对象是同一个对象if(this == obj)return true;//只有当obj是Person对象if(obj != null && obj.getClass() == Person.class){Person personObj = (Person)obj;//并且当前对象的idStr与obj对象的idStr相等时才可以判断两个对象是否相等if(this.getIdStr().equals(personObj.getIdStr())){return true;}}return false;}}
public class OverrideEqualsRight {public static void main(String[] args){Person p1 = new Person("lancibe", "19");Person p2 = new Person("babylan", "19");Person p3 = new Person("lancibe", "20");System.out.println("p1和p2是否相等?"+ p1.equals(p2));System.out.println("p2和p3是否相等?"+ p2.equals(p3));}
}
  • 通常而言,正确地重写equals()方法应该满足下列条件。
    • 自反性:对任意x,x.equals(x)一定返回true
    • 对称性:对任意x和y,如果y.equals(x)返回true,则x.equals(y)也应该返回true
    • 一致性:对任意x和y,如果判断信息没有改变,那么无论调用多少次x.equals(y)返回的结果都应该保持一致。
    • 对任何不是null的x,x.equals(null)一定返回false。
  • Object默认提供的equals()只是比较对象的地址,即与==比较的结果完全相同。因此在实际应用中常常需要重写equals方法。

final修饰符

  • final关键字可用于修饰类、变量和方法,final关键字有点类似于C#里的sealed关键字,用于表示他修饰的类、方法和变量不可改变。
  • final修饰变量时,表示该变量一旦获得了初始值就不可被改变,final既可以修饰成员变量(包括类变量和实例变量),也可以修饰局部变量、形参。

final成员变量

  • 对于final修饰的成员变量而言,一旦有了初始值,就不能被重新赋值,如果既没有在定义成员变量时指定初始值,也没有在初始化块、构造器中为成员变量指定初始值,那么这些成员变量的值将一直是系统默认分配的0、'\u0000'、false或null,这些成员变量也就完全失去了存在的意义。因此final修饰的成员变量必须由程序员显式的指定初始值
    • 类变量:必须在静态初始化块中指定初始值或声明该类变量时指定初始值,而且只能在两个地方的其中之一指定。
    • 实例变量:必须在非静态初始化块、声明该实例变量或构造器中指定初始值,而且只能在三个地方中的其中之一来指定。
public class FinalVariableTest {//定义成员变量时指定默认值,合法final int a = 6;//下面变量将在构造器或初始化块中分配初始值final String str;final int c;final static double d;//初始化块,可以对没有指定默认值的实例变量指定初始值{str = "hello";//不能为a重新赋值,因此下面语句非法//a = 9;}//静态初始化块,可以对没有指定默认值的类变量指定初始值static {d = 5.6;}//构造器,可对既没有指定初始值,又没有在初始化块中指定初始值的实例变量指定初始值public FinalVariableTest(){//如果在初始化块中已经对str指定了初始值//那么在构造器中不能对final变量赋初始值,下面赋值语句非法// str = 'java';c = 5;}public void changeFinal(){//普通方法不能为final修饰的成员变量赋值//d = 1.2;//不能再普通方法中为final成员变量指定初始值//ch = 'a';}public static void main(String[] args){FinalVariableTest ft = new FinalVariableTest();System.out.println(ft.a);System.out.println(ft.c);System.out.println(ft.d);}
}
  • 如果打算在构造器、初始化块中对final成员变量进行初始化,则不要在初始化之前访问成员变量;但Java又允许通过方法来访问final成员变量,此时会看到系统讲final成员变量赋值为0或类似功能的数值。
public class FinalErrorTest {//定义一个final修饰的实例变量//系统不会对final成员变量进行默认初始化final int age;{//age没有初始化,所以此处代码将会引起错误//System.out.println(age);printAge();age = 6;System.out.println(age);}public void printAge(){System.out.println(age);}public static void main(String[] args){new FinalErrorTest();}
}
  • 会发现上面程序输出了0、6。

final局部变量

  • 系统不会对局部变量进行初始化,局部变量必须由程序员显式初始化。因此使用final修饰局部变量时,即可以在定义时指定默认值,也可以不指定默认值。
public class LocalVariableTest {public void test(final int a){//不能对final修饰的形参赋值//a = 5;}public static void main(String[] args){//定义final局部变量时指定默认值,则str变量无法被赋值final String str = "hello";//下面语句非法//str = "lancibe";final double d;//第一次赋值,允许d = 5.6;//重复赋值,非法//d = 3.14;}
}

final修饰基本类型变量和应用类型变量的区别

  • 当使用final修饰基本类型变量时,不能对基本类型变量重新进行赋值,因此基本类型变量不能被改变。
  • 但对于引用类型变量来说,他保存的仅仅是一个引用,final只保证这个引用类型变量所引用的地址不会改变,即一直应用同一个对象,当这个对象完全可以发生改变。
package finalreferencetest;import java.util.Arrays;class Person
{private int age;public Person(){}public Person(int age){this.age = age;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}
}
public class FinalReferenceTest {public static void main(String[] args){//final修饰数组变量,iArr是一个引用变量final int[] iArr = {5,6,12,9};System.out.println(Arrays.toString(iArr));//对数组元素进行排序,合法Arrays.sort(iArr);System.out.println(Arrays.toString(iArr));//对数组元素赋值,合法iArr[2] = -9;System.out.println(Arrays.toString(iArr));//对iArr重新赋值,非法//iArr = null;//final修饰Person变量,p是一个引用变量final Person p = new Person(45);//改变Person对象的age实例变量,合法p.setAge(32);System.out.println(p.getAge());//下面语句对p重新赋值,非法//p = null;}
}
  • 除此之外,Java修饰的方法不可被重写,Java修饰的类不可被作为父类

不可变类

  • 不可变类(immutable)的意思是创建该类的实例后,该实例的实例变量是不可改变的。Java提供的8个包装类和java.lang.String类都是不可变类,当创建他们的实例后,其实例的实例变量不可变。
  • 其实可以理解为:类不提供相应的方法来修改这个实例变量的值。
  • 如果需要创建自定义的不可变类,可遵循以下规则:
    • 使用private和final修饰符来修饰该类的成员变量。
    • 提供带参数的构造器,用于根据传入参数来初始化类里的成员变量。
    • 仅为该类的成员变量提供getter()方法,不要提供setter()方法,因为普通方法无法修改final修饰的成员变量。
    • 如果有必要,重写Object类里的hashCode()equals()方法。
public class Address {private final String detail;private final String postCode;public Address(){this.detail = "";this.postCode = "";}public Address(String detail, String postCode){this.detail = detail;this.postCode = postCode;}//仅为两个实例变量提供getter方法public String getDetail() {return detail;}public String getPostCode() {return postCode;}//重写equals()方法,判断两个对象是否相等public boolean equals(Object obj){if(this == obj)return true;if(obj != null && obj.getClass() == Address.class){Address ad = (Address)obj;if(this.getDetail().equals(ad.getDetail()) && this.getPostCode().equals(ad.getPostCode()))return true;}return false;}public int hashCode(){return detail.hashCode() + postCode.hashCode() * 31;}}

如果需要设计一个不可变类,尤其要注意其引用类型的成员变量,如果引用类型的成员变量的类是可变的,就必须采取必要的措施来保护该成员变量所引用的对象不会被修改,这样才能创建真正的不可变类。

抽象类

  • 当编写一个类时,常常会为该类定义一些方法,这些方法用以描述该类的行为方式,那么这些方法都有具体的方法体。但是在某些情况下,某个父类只是知道其子类应该包含怎样的方法。

抽象方法和抽象类

  • 抽象方法和抽象类必须使用abstract修饰符来定义,有抽象方法的类只能被定义为抽象类,抽象类里可以没有抽象方法。
    • 抽象类必须使用abstract修饰符来修饰,抽象方法也必须使用它来修饰,抽象方法不能有方法体
    • 抽象类不能被实例化,无法使用new关键字来调用抽象类的构造器创建抽象类的实例。即使抽象类里不包含抽象方法,这个抽象类也不能创建实例
    • 抽象类可以包含成员变量、方法(普通方法和抽象方法都可以)、构造器、初始化块、内部类(接口,枚举)5种成分。抽象类的构造器不能用于创建实例,主要是用于被其子类调用。
    • 含有抽象方法的类(包括直接定义了一个抽象方法;或继承了一个抽象父类,但没有完全实现父类包含的抽象方法;或实现了一个接口,但没有完全实现接口包含的抽象方法;或实现了一个接口,但没有完全实现接口包含的抽象方法三种情况)只能被定义成抽象类。
  • 定义抽象方法只需要在普通方法上面加上abstract修饰符,并把普通方法的方法体(也就是方法后花括号括起来的部分)全部去掉,并在方法后增加分号即可。
public abstract class Shape {{System.out.println("执行初始化块");}private String color;//定义一个计算周长的抽象方法public abstract double calPerimeter();//定义一个返回形状的抽象方法public abstract String getType();//定义Shape的构造器,该构造器并不是用于创建Shape对象//而是用于被子类调用public Shape(){}public Shape(String color){System.out.println("执行Shape的构造器");this.color=color;}public String getColor() {return color;}public void setColor(String color) {this.color = color;}
}
  • 抽象类不能用于创建实例,只能当做父类被其他子类继承。
  • 下面定义一个三角形类,三角形类被定义成普通类,因此必须实现Shape类里的所有抽象方法。
package shape;public class Circle extends shape.Shape {private double radius;public Circle(String color, double radius){super(color);this.radius=radius;}public void setRadius(double radius){this.radius=radius;}public double calPerimeter(){return 2*Math.PI*radius;}public String getType(){return getColor() + "圆形";}public static void main(String[] args){shape.Shape s1 = new shape.Triangle("黄色", 3,4,5);shape.Shape s2 = new Circle("红色", 3);System.out.println(s1.getType());System.out.println(s1.calPerimeter());System.out.println(s2.getType());System.out.println(s2.calPerimeter());}
}
  • 执行结果如下:
执行初始化块
执行Shape的构造器
执行初始化块
执行Shape的构造器
三角形
12.0
红色圆形
18.84955592153876
  • 上面的main方法中定义了两个Shape类型的引用变量,他们分别指向Triangle对象和Circle对象,由于在Shape类中定义了calperimeter方法和getType方法,所以程序可以直接调用两个变量的该方法,无法强制类型转换为其子类类型。
  • 利用抽象类和抽象方法的优势,可以更好的发挥多态的优势,使得程序更加灵活。
  • 当使用abstract修饰类时,表明这个类只能被继承;当使用abstract修饰方法时,表明这个方法必须由子类提供实现(即重写)。而final修饰的类不能被继承,故他们两个永远不能一起被使用。

Java9 改进的接口

  • 抽象类是从多个类中抽象出来的模板,如果将这种抽象进行的更彻底,则可以提练出一种更加特殊的“抽象类”:接口。Java9对接口进行了改进,允许在接口中定义默认方法和类方法,默认方法和类方法都可以提供方法实现,Java9为接口增加了一种私有方法,私有方法也可提供方法实现。

Java9中接口的定义

  • 和类定义不同,定义接口不再使用class关键字,而是使用interface关键字,接口定义的基本语法如下:
[修饰符] interface 接口名 extends 父接口1, 父接口2...
{零个到多个常量定义零个到多个抽象方法定义零个到多个内部类、接口、枚举定义零个到多个私有方法、默认方法或类方法定义
}
  • 修饰符可以是public或省略,如果省略将采用默认权限访问控制符,即只有在相同包结构下才可以访问该接口。
  • 接口名应与类名采用相同的命名规则,即如果仅从语法角度来看,接口名只要是合法的标识符即可;如果要遵守Java可读性规范,则接口名应由多个有意义的单词连缀而成,每个单词首字母大写,单词与单词之间无需任何分隔符。接口名通常能够使用形容词。
  • 一个接口可以有多个直接父接口,但接口只能继承接口,不能继承类。
  • 由于接口定义的是一种规范,因此接口里不能包含构造器和初始化来定义。接口里可以包含成员变量(只能是静态常量)、方法(只能是抽象实例方法、类方法、默认方法或私有方法)、内部类(包括内部借口、枚举)定义。
  • 对于接口里定义的静态常量而言,他们是接口相关的,因此系统会自动为这些成员添加static和final两个修饰符。也就是说,在接口中定义成员变量时,不管是否使用public static final修饰符,接口里的成员变量总是以这三个修饰符来修饰。而且接口里没有构造器和初始化块,因此接口里定义的成员变量只能在定义时指定默认值。
  • 接口里定义的方法只能是抽象方法、类方法、默认方法或私有方法,因此如果不是定义默认方法、类方法或私有方法,系统会自动为普通方法增加abstract修饰符;定义接口里的普通方法时不管是否使用public abstract修饰符,接口里的普通方法总是以它来修饰。接口里的普通方法不能有方法实现(方法体);但类方法、默认方法、私有方法都必须有方法实现。

接口里定义的内部类、内部借口、内部枚举默认都采用public static两个修饰符,不管定义是否指定这两个修饰符,系统都会自动使用它对他们进行修饰。

  • 下面定义一个接口
public interface Output {int MAX_CHCHE_LINE = 50;//接口中定义的普通方法只能是public的抽象方法void out();void getData(String msg);//在接口中默认方法,需要使用default修饰default void print(String... msgs){for (String msg:msgs){System.out.println(msg);}}default void test(){System.out.println("默认的抽象方法");}//在接口中定义类方法,需要使用static修饰static String staticTest(){return "接口里的类方法";}//定义私有方法private void foo(){System.out.println("foo私有方法");}//定义私有静态方法private static void bar(){System.out.println("bar私有静态方法");}
}
  • 接口的默认方法其实就是实例方法,但由于早期Java的设计是:接口中的实例方法不能有方法体;Java8也不能直接“推倒”以前的规则,只好重定义一个所谓的默认方法,默认方法就是方法体的实例方法。
  • Java8允许在接口中定义类方法,类方法必须使用static修饰,该方法不能使用default修饰,无论程序是否指定,类方法总是使用public修饰——如果开发者没有指定public,系统会为其自动添加。类方法可以直接使用接口来调用。
  • Java9增加了带方法体的私有方法,这也是Java8埋下的伏笔:Java8允许在接口中定义带方法体的默认方法和类方法——这样势必会引发一个问题,当两个默认方法(或类方法)中包含一段相同的实现逻辑时,程序必然考虑将这段实现逻辑抽取成工具方法,而工具方法是应该被隐藏的,这就是Java9增加私有方法的必然性。
  • 接口里的成员变量默认是使用public static final修饰的,因此即使另一个类处于不同包下,也可以通过接口来访问接口里的成员变量。
public class OutputFieldTest {public static void main(String[] args){System.out.println(Output.MAX_CHCHE_LINE);System.out.println(Output.staticTest());}
}
  • 从上面main方法中可以看出,即使该类与output处于不同包下,仍可以访问他的成员变量,这说明他是public访问权限,而且可以通过借口来访问该成员变量,表明该成员变量是一个类变量;当为这个成员变量赋值时会引发“为final变量赋值”的编译异常,表明这个成员变量使用了final修饰。

从某种角度来看,接口可以被当成一种特殊的类,因此一个Java源文件里最多只有一个public接口,如果一个Java源文件里定义了一个public接口,则该源文件的主文件名必须与该接口名相同。

接口的继承

  • 接口的继承和类的继承不一样,接口完全支持多继承,即一个接口可以有多个直接父接口。和类继承相似,子接口扩展某个父接口,将会获得父接口里定义的所有抽象方法、常量。
  • 一个接口继承多个父接口时,多个父接口排在extends关键字之后,多个父接口之间以英文逗号隔开。下面程序定义了三个接口,第三个接口继承了前面两个接口。
interface InterfaceA {int PROP_A = 5;void testA();
}interface InterfaceB{int PROP_B = 6;void testB();
}interface InterfaceC extends InterfaceA, InterfaceB
{int PROP_C = 7;void testC();
}public class InterfaceExtendsTest
{public static void main(String[] args){System.out.println(InterfaceC.PROP_A);System.out.println(InterfaceC.PROP_B);System.out.println(InterfaceC.PROP_C);}
}

使用接口

  • 接口不能用于创建实例,但接口可以用于声明引用类型变量。当使用接口来声明引用类型变量时,这个引用类型变量必须引用到其实现类的对象。除此之外,接口的主要用途是被实现类实现。归纳起来,接口主要有如下用途。
    1. 定义变量,也可以用于强制类型转换
    2. 调用接口中定义的常量
    3. 被其他类实现
  • 一个类可以实现一个或多个接口,继承使用extends关键字,实现则使用implements关键字。因为一个类可以实现多个接口,这也是Java为单继承灵活性不足所做的补充。类实现接口的语法格式如下:
[修饰符] class 类名 extends 父类 implements 接口1,接口2...
{类体部分
}
  • 一个类实现了一个或多个接口之后,这个类必须完全实现这些接口里所定义的全部抽方法(也就是重写这些抽象方法);否则,该类讲保留从父接口那里继承到的抽象方法,该类也必须定义成抽象类。
  • 一个类实现某个接口时,该类将会获得接口中定义的常量(成员变量)、方法等,因此可以把实现接口理解成一种特殊的继承,相当于实现类继承了一个彻底抽象的类(相当于除默认方法外,所有方法都是抽象方法的类)。
//定义一个Product接口
interface Product
{int getProduceTime();
}
//让printer类实现Output和Product接口
public class Printer implements Output, Product {private String[] printData = new String[Output.MAX_CHCHE_LINE];//用以记录当前需打印的作业数private int dataNum = 0;public void out(){//只要还有作业,就继续打印while(dataNum > 0){System.out.println("打印机打印:"+printData[0]);//把作业队列整体前移一位,并将剩下的作业数减一System.arraycopy(printData, 1, printData, 0, --dataNum);}}public void getData(String msg){if(dataNum >= Output.MAX_CHCHE_LINE){System.out.println("输出队列已满,添加失败");}else{//把打印数据添加到队列里,已保存数据的数量加一printData[dataNum++] = msg;}}@Overridepublic int getProduceTime() {return 45;}public static void main(String[] args){//创建一个Printer对象,当成Output使用Output o = new Printer();o.getData("lancibe");o.getData("babyxun");o.out();o.getData("Fei");o.getData("babyfei");o.out();//调用Output接口中定义的默认方法o.print("first", "second", "third");o.test();//创建一个Printer对象,当成Product使用Product p = new Printer();System.out.println(p.getProduceTime());//所有接口类型的引用变量都可以直接赋给Object类型的变量Object obj = p;}
}
  • 从上面程序中可以看出,Printer类实现Output接口和Product接口,因此Printer对象即可以直接赋给Output变量,也可以直接赋给Product变量。仿佛Printer类既是Output的子类,也是Product的子类,这就是Java提供的模拟多继承。
  • 上面程序中Printer实现了Output接口,即可获取Output接口中定义的print()和test()两个默认方法,因此Print实例可以直接调用这两个默认方法。
  • 接口不能显式继承任何类,但所有接口类型的引用变量都可以直接赋给Object类型的引用变量。所以在上面程序中可以把Product类型的变量直接赋给Object类型变量,这是利用向上转型来实现的,因为编译器知道任何Java对象都必须是Object或其子类的实例,Product类型的对象也不例外(它必须是Product接口实现类的对象,该实现类肯定是Object的显式或隐式子类)。

接口和抽象类

  • 接口和抽象类很像,他们都具有如下特征。
    • 接口和抽象类都不能被实例化,它们都位于继承树的顶端,用于被其它类实现和继承。
    • 接口和抽象类都可以包含抽象方法,实现接口或继承抽象类的普通子类都必须实现这些抽象方法。
  • 但接口和抽象类之间的差别非常大,主要体现在二者设计目的上。
  • 接口作为系统与外界交互的窗口,接口体现的是一种规范。对于接口的实现者而言,接口规定实现者必须向外提供哪些服务(以方法的形式来提供);对于接口的调用者而言,接口规定了调用者可以调用哪些服务,以及如何调用这些服务(就是如何来调用方法)。但在一个程序中使用接口时,接口是多个模块间的耦合标准;当在多个应用程序之间使用接口时,接口是多个程序之间的通信标准。
  • 从某种程度上来看,接口类似于整个系统的“总纲”,他制订了系统各模块应该遵循的标准,因此一个系统中的接口不应该经常改变。一旦接口被改变,对整个系统甚至其他系统的影响将是辐射式的,导致系统中大部分类都需要改写。
  • 抽象类则不一样,抽象类作为系统中多个子类的共同父类,它所体现的是一种模板式设计。抽象类作为多个子类的抽象父类,可以被当成系统实现过程中的中间产品,这个中间产品已经实现了系统的部分功能(那些已经提供实现的方法),但这个产品依然不能当成最终产品,必须有更进一步的完善,这种完善可能有几种不同方式。
  • 除此之外,接口和抽象类在用发上也存在如下差别。
    • 接口里只能包含抽象方法、静态方法、默认方法和私有方法,不能为普通方法提供方法实现,抽象类则完全可以包含普通方法。
    • 接口里只能定义静态常量,不能定义普通成员变量;抽象类里都可以
    • 接口里不包含构造器;抽象类里可以包含构造器,但并不是用于创建对象,而是让子类调用这些构造器来完成属于抽象类的初始化操作。
    • 接口里不能包含初始化块;但抽象类完全可以。
    • 一个类最多只能有一个直接父类,包括抽象类;但一个类可以直接实现多个接口,通过实现多个借口可以弥补Java单继承的不足。

面向接口编程

简单工厂模式
  • 有一个场景:假设程序中有个Computer类需要组合一个输出设备,现在有两个选择:直接让Computer类组合一个Printer,或者让Computer类组合一个Output,那么到底采用哪种方式更好呢?
  • 假设使用第一种方法,如果有一天系统需要重构,需要使用Betterprinter来代替,这就需要打开Computer类的源代码进行修改。如果系统中只有一个Computer类组合了Printer还好,当时如果数量多了,这就是相当大的工作量。
  • 为了避免这一问题,工厂模式建议让Computer类组合一个Output类型的对象,将Computer类与Printer类完全分离。Computer对象实际上组合的是Printer对象还是Betterprinter对象,对Computer而言完全透明。当Printer对象切换到Betterprinter对象时,系统完全不受影响。下面是这个Computer定义代码。
public class Computer {private Output out;public Computer(Output out){this.out = out;}//定义一个模拟获取字符串输入的方法public void keyIn(String msg){out.getData(msg);}//定义一个模拟打印的方法public void print(){out.out();}
}
  • 上面的Computer类已经完全与Printer分离,只是与Output接口耦合。Computer不在负责创建Output对象,系统要提供一个Output工厂来负责生成Output对象。这个Outputfactory工厂类代码如下
public class OutputFactory {public Output getOutput(){return new Printer();}public static void main(String[] args){OutputFactory of = new OutputFactory();Computer c = new Computer(of.getOutput());c.keyIn("lancibe");c.keyIn("fei");c.print();}
}
  • 在该OutputFactory类中包含了一个getoutput()方法,该方法返回一个Output实现类的实例,该方法负责创建Output实例,具体创建哪一个实现类的对象由该方法决定(具体由该方法中的粗体部分控制,当然也可以增加更复杂的控制逻辑)。如果系统需要将Printer改为BetterPrinter实现类,只需让BetterPrinter实现Output接口,并改变OutputFactory类中的getOutput()方法即可。
  • 下面是BetterPrinter实现类的代码,BetterPrinter只是对原有的Printer进行简单修改,以模拟系统重构后的改进。
public class BetterPrinter implements Output {private String[] printData = new String[Output.MAX_CHCHE_LINE*2];//用以记录当前需打印的作业数private int dataNum = 0;public void out(){//只要还有作业,就继续打印while(dataNum > 0){System.out.println("高速打印机正在打印:"+printData[0]);//把作业队列整体前移一位,并将剩下的作业数减一System.arraycopy(printData, 1, printData, 0, --dataNum);}}public void getData(String msg){if(dataNum >= Output.MAX_CHCHE_LINE * 2){System.out.println("输出队列已满,添加失败");}else{//把打印数据添加到队列里,已保存数据的数量加一printData[dataNum++] = msg;}}
}
  • 上面的Betterprinter类也实现了Output接口,因此也可当成Output对象来使用,于是只需要把Outputfactory工厂类的getOutput()方法中的返回值改为return new BetterPrinter();即可。
  • 再次运行前面的OutputFactory.java程序,发现系统运行时已改为Betterprinter对象。
命令模式
  • 考虑这样一种场景:某个方法需要完成一个行为,当这个行为的具体实现无法确定,必须等到执行该方法时才可以确定。具体一点:假设有个方法需要遍历某个数组的数组元素,但无法确定在遍历数组元素时如何处理这些元素,需要在调用该方法时指定具体的处理行为。
  • 这个要求看起来有点奇怪:这个方法不进需要普通数据可以变化,甚至还有方法执行体也需要变化,难道把“处理行为”作为一个参数传入该方法?

在某些编程语言(如Ruby等)里,确实允许传入一个代码快作为参数。现在Java8已经增加了Lambda表达式,通过Lambda表达式可以传入代码块作为参数。

  • 对于这样的一个需求,必须把“处理行为”作为参数传入该方法,这个“处理行为”用编程实现是一段代码。那如何把这段代码传入该方法呢。
  • 可以考虑使用一个Command接口来定义一个方法,以此封装“处理行为”。下面是Command接口的代码。
public interface Command {//接口里定义的process方法用于封装“处理行为”void process(int[] target);
}
  • 上面的Command接口里定义了一个process()方法,该方法用于封装“处理行为”,但这个方法没有方法体——因为现在还无法确定这个处理行为。
  • 下面是需要处理数组的处理类,在这个处理类中包含一个process()方法,这个方法无法确定处理数组的处理行为,所以定义时该方法使用了一个Command参数,这个Command参数负责对数组的处理行为。该类的程序代码如下:
public class ProcessArray {public void process(int[] target, Command cmd){cmd.process(target);}
}
  • 通过一个Command接口,就实现了让ProcessArray类和具体“处理行为”的分离,程序使用Command接口代表了对数组的处理行为。Command接口也没有提供真正的处理,只有等到需要调用ProcessArray对象的process()方法时,才真正传入一个Command对象,才确定对数组的处理行为。
  • 下面的程序示范了对数组的两种处理方式。
public class CommandTest {public static void main(String[] args){ProcessArray pa = new ProcessArray();int[] target = {3, -4, 6, 4};//第一次处理数据,具体处理行为取决于PrintCommandpa.process(target, new PrintCommand());System.out.println("---------------");pa.process(target, new AddCommand());}
}
public class PrintCommand implements Command {public void process(int[] target){for (int tmp: target){System.out.println("迭代输出目标数组元素:"+tmp);}}
}
public class AddCommand implements Command {public void process(int[] target){int sum = 0;for(int tmp:target){sum += tmp;}System.out.println("数组元素总和是:"+sum);}
}

内部类

  • 大部分时候,类被定义成一个独立的程序单元。在某些情况下,也会把一个类放在另一个类的内部定义,这个定义在其他类内部的类就被称为内部类(有的地方也叫嵌套类),包含内部类的类也被称为外部类(有的地方也叫宿主类)。Java从JDK 1.1开始引入内部类,内部类主要有如下作用。
    • 内部类提供了更好的封装,可以吧内部类隐藏在外部类之内,不允许同一个包中的其他类访问该类。假设需要创建Cow类,Cow类需要组合一个Cowleg对象,Cowleg类只有在Cow类里才有效,离开了Cow类之后没有任何意义。在这种情况下,就可以把Cowleg定义成Cow的内部类,不允许其他类访问Cowleg。
    • 内部类成员可以直接访问外部类的私有数据,因为内部类被当成其外部类成员,同一个类的成员之间可以相互访问。但外部类不能访问内部类的实现细节,例如内部类的成员变量。
    • 匿名内部类适合用于创建那些仅需要一次使用的类。对于前面介绍的命令模式,但需要传入一个Command对象时,重新专门定义Printcommand和Addcommand两个实现类可能没有太大意义,因此这两个实现类可能仅需要使用一次。在这种情况下,使用匿名内部类将更加方便。
  • 从语法角度来看,定义内部类与定义外部类的语法大致相同,内部类除需要定义在其他类里面之外,还存在如下两点区别。
    • 内部类比外部类可以多使用三个修饰符:private、protected、static——外部类不可以使用这三个修饰符
    • 非静态内部类不能拥有静态成员。

非静态内部类

  • 定义内部类非常简单,只要把一个类放在另一个类内部定义即可。此处的“类内部”包括类中的任何位置,甚至在方法中也可以定义内部类(方法里定义的内部类被称为局部内部类)。
  • 下面程序在Cow类里定义了一个Cowleg非静态内部类,并在Cowleg类的实例方法中直接访问Cow的private访问权限的实例变量。
public class Cow {private double weight;//外部类的两个重载的构造器public Cow(){}public Cow(double weight){this.weight = weight;}//定义一个非静态内部类private class CowLeg{//非静态内部类的两个实例变量private double length;private String color;//非静态内部类的两个重载的构造器public CowLeg(){}public CowLeg(double length, String color){this.length = length;this.color = color;}public String getColor() {return color;}public double getLength() {return length;}public void setColor(String color) {this.color = color;}public void setLength(double length) {this.length = length;}//非静态内部类的实例方法public void info(){System.out.println("当前牛腿的颜色是:"+color+",高:"+length);//直接访问外部类的private修饰的成员变量System.out.println("本牛腿所在的奶牛重:"+weight);}}public void test(){CowLeg cl = new CowLeg(1.12, "黑白相间");cl.info();}public static void main(String[] args){Cow cow = new Cow(378.9);cow.test();}
}
  • 编译上面程序,看到文件中生成了两个class文件,一个是Cow.class,一个是CowCowLeg.class。可见,内部类的class文件总是以这种形式:‘OuterClassCowLeg.class。可见,内部类的class文件总是以这种形式:`OuterClassCowLeg.classclassOuterClassInnerClass.class`
  • 当在非静态内部类的方法内访问某个变量时,系统会优先查找该方法内是否有局部变量,再查找内部类是否存在,再查找外部类,如果依然不存在,则编译错误:找不到该变量。
  • 因此,如果外部类成员变量、内部类成员变量与内部类里方法的局部变量同名,则可以通过this、外部类类名.this作为限定来区分。
public class DiscernVariable {private String prop = "外部类的实例变量";private class InClass{private String prop = "内部类的实例变量";public void info(){String prop = "局部变量";//通过外部类类名.this.varName访问外部类实例变量System.out.println(DiscernVariable.this.prop);//通过this.varName访问内部类实例的变量System.out.println(this.prop);//直接访问局部变量System.out.println(prop);}}public void test(){InClass in = new InClass();in.info();}public static void main(String[] args){new DiscernVariable().test();}
}
  • 上面程序中粗体字代码行分别访问外部类的实例变量、非静态内部类的实例变量。通过OutterClass.this.propName的形式访问外部类的实例变量,通过this.propName的形式访问非静态内部类的实例变量。
  • 非静态内部类的成员可以访问外部类的private成员,但反过来就不成立了。非静态内部类的成员只在非静态内部类范围内是可知的,并不能被外部类直接使用。如果外部类需要访问非静态内部类的成员,则必须显式创建非静态内部类对象来调用访问其实例成员。
public class Outer {private int outProp = 9;class Inner{private int inProp = 5;public void accessOuterProp(){//非静态内部类可以直接访问外部类的private成员变量System.out.println("外部类的outProp值:"+outProp);}}public void accessInnerProp(){//外部类不能直接访问非静态内部类的实例变量//下面代码编译错误//System.out.println("内部类的inProp值:"+inProp);//如需访问内部类的实例变量,必须显式创建内部类对象System.out.println("内部类的inProp值:"+new Inner().inProp);}public static void main(String[] args){//执行下面代码,只创建了外部类对象,还未创建内部类对象Outer out = new Outer();out.accessInnerProp();}
}
  • 根据静态成员不能访问非静态成员的规则,外部类的静态方法、静态代码块不能访问非静态内部类,包括不能使用非静态内部类定义变量、创建实例等。总之,不允许在外部类的静态成员中直接使用非静态内部类。
public class StaticTest {//定义一个非静态的内部类,是一个空类private class In{}//外部类的静态方法public static void main(String[] args){//下面代码引起编译异常,因为静态成员(main()方法)无法访问非静态成员(In类)//new In();}
}
  • Java不允许在非静态内部类里定义静态成员。下面程序示范了非静态内部类包含静态成员将引发编译错误
public class InnerNoStatic {private class InnerClass{static{System.out.println("=====");}private static int inProp;private static void test(){};}
}
  • 非静态内部类里不能有静态方法、静态成员变量、静态初始化块。所以上面三个静态声明都会引发错误。

静态内部类

  • 如果使用static来修饰一个内部类,则这个内部类就属于外部类本身,而不属于外部类的莫个对象。因此使用static修饰的内部类称为类内部类,也称为静态内部类。
  • 静态内部类可以包含静态成员,也可以包含非静态成员。根据静态成员不能访问非静态成员的规则,静态内部类不能访问外部类的实例成员,只能访问外部类的类成员。即使静态内部类的实例方法,也不能访问外部类的实例成员,只能访问外部类的静态成员。
public class StaticInnerClassTest {private int prop1 = 5;private static int prop2 = 9;static class StaticInnerClass{//静态内部类里可以包含静态成员private static int age;public void accessOuterProp(){//如果访问prop1,则出现错误System.out.println(prop2);}}
}
  • 外部类依然不能直接访问静态内部类的成员,但是可以使用静态内部类的类名作为调用者来访问静态内部类的类成员,也可以使用静态内部类对象作为调用者来访问静态内部类的实例成员。
public class AccessStaticInnerClass {static class StaticInnerClass{private static int prop1 = 5;private int prop2 = 9;}public void accessInnerProp(){//类名.变量名System.out.println(StaticInnerClass.prop1);//调用构造器并访问System.out.println(new StaticInnerClass().prop2);}
}
  • 除此之外,Java还允许在接口里定义内部类,接口里定义的内部类默认使用public static修饰,也就是说,接口内部类只能是静态内部类。

局部内部类

  • 如果把一个内部类放在方法里定义,则这个内部类就是一个局部内部类,局部内部类仅在该方法内有效。由于局部内部类不能再外部类的方法意外的地方使用,因此局部内部类也不能使用访问控制符static修饰符来修饰。
public class LocalInnerClass {public static void main(String[] args){//定义局部内部类class InnerBase{int a;}class InnerSub extends InnerBase{int b;}//创建局部内部类的对象InnerSub is = new InnerSub();is.a = 5;is.b = 9;System.out.println(""+(is.a+is.b));}
}

匿名内部类

  • 匿名内部类适合创建那种只需要一次使用的类,例如前面介绍命令模式时需要的Command对象。匿名内部类的语法有点奇怪,创建匿名内部类时会立即创建一个该类的实例,这个类定义会立即消失,匿名内部类不能重复使用。
  • 创建匿名内部类的语法格式如下
new 实现接口() | 父类构造器(实参列表)
{//匿名内部类的类体部分
}
  • 最常用的创建匿名内部类的方式是需要创建某个接口类型的对象,如下所示
interface Product
{public double getPrice();public String getName();
}
public class AnonymousTest {public void test(Product p){System.out.println("购买了一个"+p.getName()+",花掉了"+p.getPrice());}public static void main(String[] args){AnonymousTest ta = new AnonymousTest();ta.test(new Product() {@Overridepublic String getName(){return "lancibe";}public double getPrice(){return 567.8;}});}
}
  • 当通过实现接口来创建匿名内部类时,匿名内部类也不能显式创建构造器,因此匿名内部类只有一个隐式的无参数构造器,故new接口名后的括号里不能传入参数值。但如果通过继承父类来创建匿名内部类时,匿名内部将拥有和父类相似的构造器,此处的相似指的是拥有相同的形参列表。
abstract class Device
{private String name;public abstract double getPrice();public Device(){};public Device(String name){this.name = name;}public String getName() {return name;}public void setName(String name) {this.name = name;}
}
public class AnonymousInner {public void test(Device d){System.out.println("购买了一个"+d.getName()+",花掉了"+d.getPrice());}public static void main(String[] args){AnonymousInner ai = new AnonymousInner();ai.test(new Device("电子示波器") {@Overridepublic double getPrice() {return 67.8;}});//调用无参数构造器创建Device匿名实现类的对象Device d = new Device() {//初始化块{System.out.println("匿名内部类的初始化块");}@Overridepublic double getPrice() {return 56.2;}//重写父类的实例方法public String getName(){return "键盘";}};ai.test(d);}
}

Java8 新增的Lambda表达式

  • Lambda表达式是Java8的重要更新,他允许使用更简洁的代码来创建只有一个抽象方法的接口(函数式接口)的实例

Lambda表达式入门

  • 使用匿名内部类来改写前面介绍的Command表达式的例子,改写后的程序如下:
public class CommandTest {public static void main(String[] args){ProcessArray pa = new ProcessArray();int[] target = {3, -4, 6, 4};//处理数组,具体处理行为取决于匿名内部类pa.process(target, new Command() {public void process(int[] target){int sum = 0;for(int tmp:target){sum += tmp;}System.out.println("数组元素的总和是:"+sum);}});}
}
  • Lambda表达式完全可用于创建匿名内部类对象,因此可将上面代码改为如下形式
public class CommandTest2 {public static void main(String[] args){ProcessArray pa = new ProcessArray();int[] array = {3,-4,6,4};//处理数组,具体处理行为取决于匿名内部类pa.process(array, (int[] target)->{int sum = 0;for(int tmp:target){sum += tmp;}System.out.println("数组元素的总和是:"+sum);});}
}
  • 从上面程序中的粗体字代码可以看出,这段代码与创建匿名内部类时所需要实现的process(int[] target)方法完全相同,只是不需要new Xxx(){}这样繁琐的代码,不需要指出重写的方法名字,也不需要给出重写的方法的返回值类型——只要给出重写的方法括号以及括号内的形参列表即可。
  • 从上面介绍可以看出,当使用Lambda表达式代替匿名内部类创建对象时,Lambda表达式的代码块将代替实现抽象方法的方法体,Lambda表达式就相当于一个匿名方法。他有三部分组成:
    1. 形参列表。形参列表允许省略形参类型。如果形参列表中只有一个参数,甚至连形参列表的圆括号也可以省略。
    2. 剪头->
    3. 代码块。如果代码块只包含一条语句,Lambda表达式允许省略代码块的花括号。Lambda表达式需要返回值,而他的代码块仅有一条省略的return语句,Lambda表达式会自动返回这条语句的值。
  • 下面程序示范了Lambda表达式的几种简化写法
interface Eatable
{void taste();
}
interface Flyable
{void fly(String weather);
}
interface Addable
{int add(int a, int b);
}public class LambdaQs {//调用该方法需要Eatable对象public void eat(Eatable e){System.out.println(e);e.taste();}//调用该方法需要Flyable对象public void drive(Flyable f){System.out.println("我正在驾驶:"+f);f.fly("晴天");}//调用该方法需要Addablepublic void test(Addable add){System.out.println("5和3的和是"+add.add(5,3));}public static void main(String[] args){LambdaQs lq = new LambdaQs();lq.eat(()->System.out.println("苹果味道不错"));lq.drive(weather->{System.out.println("今天天气是:"+weather);System.out.println("直升机飞行平稳");});lq.test((a,b)->a+b);}
}
  • 上面程序可以正常编译运行,说明lambda表达式实际上将会被当成一个任意类型的对象,到底需要当成各种类型的对象,取决于运行环境的需要。

Lambda表达式与函数式接口

  • Lambda表达式的目标类型必须是“函数式接口”。函数式接口只包含一个抽象方法的接口。函数式接口可以包含多个默认方法、类方法,但只能声明一个抽象方法。
  • 如果采用匿名内部类语法来创建函数式接口的实例,则只需要实现一个抽象方法,在这种情况下即可采用Lambda表达式来创建对象,该表达式创建出来的对象的目标类型就是这个函数式接口。查阅Java8的API文档,可以发现大量的函数式接口,例如:Runnable、ActionListener等接口都是函数式接口。

Java8专门为函数式接口提供了@FunctionalInterface注解,需放在接口定义的前面,它用于告诉编译器执行更严格检查——检查该接口必须是函数式接口,否则编译器会报错。

  • Lambda表达式的结果是被当成对象,因此程序中可以使用Lambda表达式来进行赋值:
Runnable r = ()->{
for (int i = 0 ; i < 100 ; i++)
{System.out.println();
}
};
  • Lambda表达式的目标类型必须是明确的函数式接口;Lambda表达式只能为函数式接口创建对象。Lambda表达式只能实现一个方法,因此他只能为只有一个抽象方法的接口(函数式接口)创建对象。
  • 为保证Lambda表达式的目标类型是一个明确的函数式接口,可以有如下三种常见方式:
    1. 将Lambda表达式赋值给函数式接口类型的变量
    2. 将Lambda表达式作为函数式接口类型的参数传给某个方法
    3. 使用函数式接口对Lambda表达式进行强制类型转换
Object obj1 = (Runnable)()->{
for (int i = 0 ; i < 100 ; i++)
{System.out.println();
}
};
  • 函数式接口的定义如下:
@FunctionalInterface
interface FkTest
{void run();
}
  • 上面的函数式接口中仅定义了一个不带参数的方法,因此前面强制类型转换的Runnable也可以换成FkTest类型。
Object obj2 = (FkTest)() ->{for(int i = 0 ; i < 100 ; i++){System.out.println();}
};

方法引用与构造器引用

  • Lambda表达式支持的方法引用和构造器引用
种类示例说明对应的Lambda表达式
引用类方法类名::类方法函数式接口中被实现方法的全部参数传给该类方法作为参数(a,b,…)->类名.类方法(a,b,…)
引用特定对象的实例方法特定对象::实例方法函数式接口中被实现方法的全部参数传给该方法作为参数(a,b,…)->特定对象.实例方法(a,b,…)
引用某类对象的实例方法类名::实例方法函数式接口中被实现方法的第一个参数作为调用者,后面的参数全部传给该方法作为参数(a,b,…)->a.实例方法(b,…)
引用构造器类名::new函数式接口中被实现方法的的全部参数传给该构造器作为参数(a,b,…)->new 类名(a,b,…)
  • 对上面四种情况分别举例:
import javax.swing.*;@FunctionalInterface
interface Converter
{Integer convert(String from);
}@FunctionalInterface
interface MyTest
{String test(String a, int b, int c);
}@FunctionalInterface
interface YourTest
{JFrame win(String title);
}public class MethodRefer {public static void main(String[] args){//引用类方法//Converter converter1 = from-> Integer.valueOf(from);Converter converter1 = Integer::valueOf;//两种写法都可Integer val = converter1.convert("99");System.out.println(val);//引用特定对象的实例方法//Converter converter2 = from -> "lancibe.org".indexOf(from);Converter converter2 = "lancibe.org"::indexOf;Integer value = converter2.convert("lan");System.out.println(value);//引用某类对象的实例方法//MyTest mt = (a,b,c) -> a.substring(b,c);MyTest mt = String::substring;String str = mt.test("I love Java", 2, 9);System.out.println(str);//引用构造器//YourTest yt = (String a) -> new JFrame(a);YourTest yt = JFrame::new;JFrame jf = yt.win("my window");System.out.println(jf);}
}

Lambda表达式与匿名内部类的区别和联系

  • 相同点:
    1. Lambda表达式与匿名内部类一样都可以直接访问“effectively final”的局部变量,以及外部类的成员变量(包括实例变量和类变量)。
    2. Lambda表达式创建的对象与匿名内部类生成的对象一样,都可以直接调用从接口中继承的默认方法。
@FunctionalInterface
interface Displayable
{void display();default int add (int a , int b){return a+b;}
}
public class LambdaAndInner {private int age = 12;private static String name = "1909";public void test(){String book = "Lancibe";Displayable dis = ()->{//访问"effectively final"的局部变量System.out.println("book的局部变量为:"+book);System.out.println("外部类的age实例变量为:"+age);System.out.println("外部类的name类变量为:"+name);};dis.display();System.out.println(dis.add(3, 5));}public static void main(String[] args){LambdaAndInner lambda = new LambdaAndInner();lambda.test();}
}

使用Lambda表达式调用Arrays的类方法

  • 前面介绍Array类的功能时已经提到,Arrays类的有些方法需要Comparator,XxxOperator,XxxFunction等接口的实例,这些接口都是函数式接口,因此可以使用Lambda表达式来调用Arrays的方法:
import java.util.Arrays;public class LambdaArrays {public static void main(String[] args){String[] arr1 = new String[]{"java","lancibe","fkit", "ios", "android"};Arrays.parallelSort(arr1, (o1, o2)->o1.length()-o2.length());System.out.println(Arrays.toString(arr1));int[] arr2 = new int[] {3,-4,25,16,30,18};Arrays.parallelPrefix(arr2, (left, right)->left*right);System.out.println(Arrays.toString(arr2));long[] arr3 = new long[5];Arrays.parallelSetAll(arr3, operand -> operand*5);System.out.println(Arrays.toString(arr3));}
}
  • 其结果如下:
[ios, java, fkit, lancibe, android]
[3, -12, -300, -4800, -144000, -2592000]
[0, 5, 10, 15, 20]
  • Lambda表达式的作用在:第一段指定了判断字符串大小的方式,第二段根据前后两个元素计算当前元素的值,第三段会根据元素的索引来计算当前元素的值。

枚举类

  • 在某些情况下,一个类的对象是有限且固定的,比如季节类,他只有四个对象。这种实例有限而且固定的类,在Java里称为枚举类。
  • enum关键字(他与class、interface关键字的地位相同)用于定义枚举类。
  • 它与普通类有如下简单区别:
    1. 枚举类可以实现一个或多个接口,使用enum定义的枚举类默认继承了java.lang.Enum类,而不是默认继承Object类,因此枚举类不能显式继承其他父类。其中java.lang.Enum类实现了java.lang.Serializable和java.lang.Comparable两个接口。
    2. 使用enum定义、非抽象的枚举类默认会使用final修饰,因此枚举类不能派生子类。
    3. 枚举类的构造器只能使用private访问控制符,如果省略了控制符,系统会自动添加private。
    4. 枚举类的所有实例必须放在枚举类的第一行显式列出,否则这个枚举类永远都不能产生实例。列出这些实例时,系统会自动添加public static final修饰,无需程序员显式添加。
  • 下面程序定义了一个SeasonEnum枚举类。
public enum SeasonEnum {//要注意,如果要实现枚举类里的抽象方法,则每个变量后面应该有一个大括号,内部进行抽象方法的实现SPRING,SUMMER,FALL,WINTER;
}
  • 如果需要使用该枚举类的某个实例,则可使用EnumClass.variable的形式:
public class EnumTest {public void judge(SeasonEnum s){//switch语句里的表达式可以是枚举值switch (s){case SPRING:System.out.println("spring");break;case SUMMER:System.out.println("summer");break;case FALL:System.out.println("fall");break;case WINTER:System.out.println("winter");break;}}public static void main(String[] args){for(SeasonEnum a : SeasonEnum.values()){System.out.println(a);}//使用实例时,可以通过EnumClass.variable来访问new EnumTest().judge(SeasonEnum.SPRING);}
}
  • java.lang.Enum类中提供了如下几个方法:
int compareTo(E o);//该方法用于与指定枚举对象比较顺序,同一个枚举实例只能与相同类型的枚举实例进行比较。如果该枚举对象位于指定枚举对象之后,则返回正整数;如果该枚举对象位于指定枚举对象之前,则返回负整数,否则返回0
String name();//返回此枚举实例的名称,这个名称就是定义枚举类时列出的所有枚举值之一。与此方法相比,大多数程序员应优先考虑toString()方法,因为后者返回更加用户友好的名称
int ordinal();//返回枚举值在枚举类中的索引值(就是枚举值在枚举声明中的位置,第一个枚举值的索引值为0)
String toString();//返回枚举常量的名称,与name方法相似,但toString方法更常用
public static <T extends Enum<T>> T valueOf(Class<T> enumType, String name);//这是一个静态方法,用于返回指定枚举值中指定名称的枚举值。名称必须与在该枚举值中声明枚举值时所用的标识符完全匹配,不允许使用额外的空白字符。

对象与垃圾回收

对象在内存中的状态

  • 可达状态:当一个对象被创建后,若有一个以上的引用变量引用他,这该对象在程序中处于可达状态,程序可以通过引用变量来调用该对象的实例变量和方法
  • 可恢复状态:如果程序中某个对象不再有任何引用变量引用它,就进入了可恢复状态,在这种状态下,系统的垃圾回收机制准备回收该对象所占用的内存。在回收前,系统将调用所有可恢复状态对象的finalize()方法进行资源清理。如果系统在调用该方法时有一个引用变量引用了该对象,则这个对象会再次变为可达状态;否则该对象将进入不可达状态
  • 不可达状态:当对象与所有引用变量的关联都被切断时进入该状态。此时系统才会真正回收该对象所占有的资源。

对象的软、虚、弱引用

  1. 强引用:最常见的方式。处于可达状态。
  2. 软引用:需要通过SoftReference实现,此时对象有可能被垃圾回收机制回收,根据内存空间决定。
  3. 弱引用:需要通过WeakReference实现,此时不管内存状态,对象都会被垃圾回收机制运行时回收。
  4. 虚引用:需要通过PhantomReference实现,他和没有引用的效果类似,他主要用于跟踪对象被垃圾回收的状态,它不能单独使用,必须和引用队列(ReferenceQueue)联合使用。
  • 上面三个引用类都包含了一个get()方法,用于获取它们所引用的对象。

修饰符的适用范围

  • 以下为Java修饰符适用范围总表
外部类、接口成员属性方法构造器初始化块成员内部类局部成员
public
protected
包访问控制符--
private
abstract
final
static
strictfp
synchronized
native
transient
volatile
default
  • strivtfp就是精确浮点,可以使浮点运算更加精确。
  • native关键字主要用于修饰一个方法,使用native修饰的方法类似于一个抽象方法,但是他通常需要C语言实现。

Java9 的多版本JAR包

  • 使用JAR包有以下好处:
    1. 安全。能够对JAR文件进行数字签名,只让能够识别数字签名的用户使用里面的东西。
    2. 加快下载速度
    3. 压缩
    4. 包封装
    5. 可移植性。JAR包作为内嵌在Java平台内部处理的标准,能够在各种平台上直接使用。

jar命令详解

  • 创建JAR文件:jar cf test.jar -C dist/ .
  • 创建JAR文件,并显示压缩过程:jar cvf test.jar -C dist/ .
  • 不使用清单文件:jar cvfM test.jar -C dist/ .
  • 自定义清单文件内容:jar cvfm test.jar manifest.mf -C dist/ .
  • 查看JAR包内容:jar tf test.jar
  • 查看JAR包详细内容:jar tvf test.jar
  • 解压缩:jar xf test.jar
  • 带提示信息解压缩:jar xvf test.jar
  • 更新JAR文件:jar uf test.jar Hello.class
  • 更新时显示详细信息:jar uvf test.jar Hello.class
  • 创建多版本JAR包:jar cvf test.jar -C dist7/ . --release 9 -C dist/ .

创建可执行的JAR包

jar cvfe test.jar test.Test test
  • 上述命令表示把test目录下的所有文件都压缩到test.jar包中,并指定使用test.Test类作为程序的入口。
  • 运行上面的JAR包有两种方式
    1. 使用java命令,java -jar test.jar
    2. 使用javaw命令,javaw test.jar
查看全文
如若内容造成侵权/违法违规/事实不符,请联系编程学习网邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!

相关文章

  1. webpack优化之缓存

    1.babel缓存 假设我们有100个js文件&#xff0c;改动其中的一个&#xff0c;那应该是只有这一个重新编译&#xff0c;其他的99个不需要再次编译&#xff0c;这里需要Babel做缓存&#xff0c;只需要在babel-loader中加入cacheDirectory配置即可。 babel缓存让第二次打包构建速度…...

    2023/12/25 8:19:53
  2. 写天猫尾部部分

    写天猫尾部部分 效果图 这个尾部部分分为三部分&#xff0c;分别是 售后保障部分 天猫支付方式等部分 友商链接&#xff0c;标识部分 整体书写的代码 <!-- 尾部部分 --><!-- 尾部大盒子部分 --><div class"footer"><!-- 图片盒子部分 -->…...

    2024/4/22 13:41:50
  3. 刷题--11/17

    学习内容&#xff1a; 为了更有条理地学习&#xff0c;我对每个知识点进行巩固练习&#xff0c;今天做了三道简单的链表题目 1、 合并两个有序链表 2、 两个链表的公共点 3、 判断链表中是否有环 一、 合并两个有序链表 问题&#xff1a;将两个有序的链表合并为一个新链表&…...

    2024/2/5 17:14:13
  4. Arrays类

    Arrays类 数组的工具类&#xff1a;java.util.Arrays 由于数组对象本身并没有什么方法可以供我们调用&#xff0c;但API中提供了一个工具Arrays供我们使用&#xff0c;从而可以对数据对象进行一些基本的操作。 Arrays类中的方法都是static修饰的静态方法&#xff0c;在使用的…...

    2024/4/20 7:39:09
  5. 按键电平保持电路--无单片机

    1、输入5V电压为例分析&#xff0c;K1断开&#xff0c;5V电压通过R4给C2充电&#xff0c;经过R7到GND&#xff0c;此时&#xff0c;通过R6跟R4的分压&#xff0c;C2电压左正右负&#xff0c;电压大概为1.19V左右&#xff0c;Q1&#xff0c;Q2都不导通&#xff0c;整个电路电流消…...

    2024/4/23 17:29:51
  6. A*算法(超级详细讲解,附有举例的详细手写步骤)

    背景&#xff1a;项目需要接触此算法&#xff0c;以下是一些自学成果&#xff0c;如有不足之处&#xff0c;欢迎指出&#xff0c;必虚心接受。做了一份PPT来汇报&#xff0c;此处直接使用自己PPT的截图。部分图片来源网络&#xff0c;如有侵权立马删除&#xff0c;以下博文仅作…...

    2023/10/25 13:42:58
  7. 基础练习——python特殊的数字——2020.11.17

    #项目名称&#xff1a; #项目简介&#xff1a; #作 者&#xff1a;key""" 资源限制 时间限制&#xff1a;1.0s 内存限制&#xff1a;512.0MB 问题描述153是一个非常特殊的数&#xff0c;它等于它的每位数字的立方和&#xff0c;即1531*1*15*5*53*3*3。编程求…...

    2024/2/5 17:28:44
  8. ubuntu系统开机自挂载硬盘

    文章目录1.硬盘识别2.格式化新硬盘3.挂载到指定目录4.查看磁盘分区的UUID5.配置开机自挂载:6.测试1.硬盘识别 $ sudo fdisk -l 2.格式化新硬盘 $ sudo mkfs.ext4 /dev/sdb13.挂载到指定目录 假如说home区下有一个/diskfile文件,我们可以把硬盘挂载到该目录下 $ sudo mount …...

    2024/4/28 3:59:14
  9. 蓝桥python——REPEAT 程序

    试题 C: REPEAT 程序 本题总分&#xff1a;10 分 【问题描述】 附件 prog.txt 中是一个用某种语言写的程序。 其中 REPEAT k 表示一个次数为 k 的循环。循环控制的范围由缩进表达&#xff0c; 从次行开始连续的缩进比该行多的&#xff08;前面的空白更长的&#xff09;为循环包…...

    2024/2/23 20:00:16
  10. 面试经验之BATMJ大厂面试Java岗位

    熟练掌握java是很关键的&#xff0c;大公司不仅仅要求你会使用几个api&#xff0c;更多的是要你熟悉源码实现原理&#xff0c;甚至要你知道有哪些不足&#xff0c;怎么改进&#xff0c;还有一些java有关的一些算法&#xff0c;设计模式等等。 1、基础知识 java中和equals和has…...

    2024/2/26 0:36:48
  11. java的IO

    本文转载&#xff1a;https://blog.csdn.net/weixin_45676630/article/details/105691569 1.什么是IO流以及IO的作用 1.1 什么是IO I/O实际上是input和output&#xff0c;也就是输入和输出。而流其实是一种抽象的概念&#xff0c;它表示的是数据的无结构化传递。 1.2 IO的作用…...

    2024/2/10 2:44:04
  12. 小白入门 - PHP的基本语法(不完全篇)

    学不积硅步&#xff0c;无以至千里 PHP 语法 PHP 脚本在服务器上执行&#xff0c;然后将纯 HTML 结果发送回浏览器。 基本的 PHP 语法 PHP 脚本可以放在文档中的任何位置。 PHP 脚本以 <?php** 开始&#xff0c;以 **?> 结束&#xff1a; <?php // PHP 代码 ?&…...

    2024/2/1 0:16:50
  13. P3327 [SDOI2015]约数个数和(莫比乌斯反演)

    题目链接&#xff1a;点击这里 题目大意&#xff1a; 设 d(x)d(x)d(x) 为 xxx 的约数个数&#xff0c;有 ttt 组数据&#xff0c;每组数据给定 n,mn,mn,m 求&#xff1a; ∑i1n∑j1md(ij)\sum_{i1}^n\sum_{j1}^md(ij)i1∑n​j1∑m​d(ij) 题目分析&#xff1a; 这个题的一个难…...

    2024/4/17 20:42:11
  14. SpringBoot~使用DruidDataSource实现日志监控

    文章目录DruidDataSource简介实现步骤DruidDataSource简介 Druid 是阿里巴巴开源平台上一个数据库连接池实现&#xff0c;结合了 C3P0、DBCP 等 DB 池的优点&#xff0c;同时加入了日志监控。Druid 可以很好的监控 DB 池连接和 SQL 的执行情况&#xff0c;天生就是针对监控而生…...

    2023/11/10 21:33:13
  15. PyTorch项目使用TensorboardX进行训练可视化

    PyTorch项目使用TensorboardX进行训练可视化 之前一直在用tensoboardx可视化&#xff0c;每次使用都会忘记细节&#xff0c;在这里整理下~&#xff0c;开始学习吧 1. 入门&#xff1a;什么是TensorboardX Tensorboard 是 TensorFlow 的一个附加工具&#xff0c;可以记录训练…...

    2023/11/14 0:25:45
  16. 5.8.1 设置多个data目录

    一台机器上的每个mysql实例应该有自己的data目录。该位置可使用–datadirdir_name选项来指定。 为一个新的实例设置data目录有不同的方法&#xff1a;  创建一个新的data目录  复制一个已存在的data目录 接下来的讨论提供了每种方法的细节。 警告 通常&#xff0c;您不应该…...

    2024/4/23 15:48:57
  17. IDEA如何双开java程序

    问题&#xff1a;当我们想要重复运行一个java程序的时候会出现如下的情况 解决&#xff1a;右键点击Edit ‘JMSCon…’ 入图勾选...

    2023/11/19 18:39:53
  18. GitHub入门一些基本概念

    GitHub入门一些基本概念 目的&#xff1a;借助GitHub托管项目代码 官网&#xff1a;github.com Repository&#xff08;仓库&#xff09;&#xff1a;用来存放项目代码&#xff0c;每个项目对应一个仓库&#xff0c;多个开源项目则有多个仓库。 Star&#xff08;收藏&#xff…...

    2024/2/24 16:39:49
  19. jmeter JSON/YAMLPath Extractor 使用解析

    1.jmeter 信息 1.jmeter 版本 &#xff1a; 3.12.组件名称&#xff1a; json/yamlpath extractor2.使用场景 适合当用户需要提取接口返回的json数据作为下一个链路的请求场景可以调用该组件3. 使用方法 如下所示&#xff1a; 1.打开jmeter &#xff0c;点击添加–后置处理器 …...

    2024/4/27 1:49:34
  20. 面试又双叒叕被刷了?大厂面试这些知识点是你必备的(面试必备知识点)

    面试前言一、java面试题1. java基础面试知识点2. java深入源码级的面试题3. 数据结构4. 线程、多线程和线程池5.并发编程有关知识点总结前言 最近有很多小伙伴跟我吐槽现在的面试越来越难了&#xff0c;去面试老是被刷&#xff0c;感觉面试官就是在耍自己。为此&#xff0c;我…...

    2024/3/23 20:14:34

最新文章

  1. n-Track Studio Suite for Mac激活版:打造您的专属音频工作室

    n-Track Studio Suite for Mac是一款功能强大的数字音频工作站软件&#xff0c;让您在家中就能享受到专业录音棚的待遇。无论是录制人声、乐器还是MIDI序列&#xff0c;都能轻松应对。 n-Track Studio Suite for Mac激活版下载 这款软件拥有实时音高校准、时间拉伸和自动补足功…...

    2024/5/5 9:36:06
  2. 梯度消失和梯度爆炸的一些处理方法

    在这里是记录一下梯度消失或梯度爆炸的一些处理技巧。全当学习总结了如有错误还请留言&#xff0c;在此感激不尽。 权重和梯度的更新公式如下&#xff1a; w w − η ⋅ ∇ w w w - \eta \cdot \nabla w ww−η⋅∇w 个人通俗的理解梯度消失就是网络模型在反向求导的时候出…...

    2024/3/20 10:50:27
  3. C++ //练习 11.14 扩展你在11.2.1节练习(第378页)中编写的孩子姓到名的map,添加一个pair的vector,保存孩子的名和生日。

    C Primer&#xff08;第5版&#xff09; 练习 11.14 练习 11.14 扩展你在11.2.1节练习&#xff08;第378页&#xff09;中编写的孩子姓到名的map&#xff0c;添加一个pair的vector&#xff0c;保存孩子的名和生日。 环境&#xff1a;Linux Ubuntu&#xff08;云服务器&#x…...

    2024/5/5 8:38:52
  4. 基于单片机的数字万用表设计

    **单片机设计介绍&#xff0c;基于单片机的数字万用表设计 文章目录 一 概要二、功能设计设计思路 三、 软件设计原理图 五、 程序六、 文章目录 一 概要 基于单片机的数字万用表设计概要是关于使用单片机技术来实现数字万用表功能的一种设计方案。下面将详细概述该设计的各个…...

    2024/5/4 12:58:13
  5. 416. 分割等和子集问题(动态规划)

    题目 题解 class Solution:def canPartition(self, nums: List[int]) -> bool:# badcaseif not nums:return True# 不能被2整除if sum(nums) % 2 ! 0:return False# 状态定义&#xff1a;dp[i][j]表示当背包容量为j&#xff0c;用前i个物品是否正好可以将背包填满&#xff…...

    2024/5/4 12:05:22
  6. 【Java】ExcelWriter自适应宽度工具类(支持中文)

    工具类 import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.CellType; import org.apache.poi.ss.usermodel.Row; import org.apache.poi.ss.usermodel.Sheet;/*** Excel工具类** author xiaoming* date 2023/11/17 10:40*/ public class ExcelUti…...

    2024/5/4 11:23:32
  7. Spring cloud负载均衡@LoadBalanced LoadBalancerClient

    LoadBalance vs Ribbon 由于Spring cloud2020之后移除了Ribbon&#xff0c;直接使用Spring Cloud LoadBalancer作为客户端负载均衡组件&#xff0c;我们讨论Spring负载均衡以Spring Cloud2020之后版本为主&#xff0c;学习Spring Cloud LoadBalance&#xff0c;暂不讨论Ribbon…...

    2024/5/4 14:46:16
  8. TSINGSEE青犀AI智能分析+视频监控工业园区周界安全防范方案

    一、背景需求分析 在工业产业园、化工园或生产制造园区中&#xff0c;周界防范意义重大&#xff0c;对园区的安全起到重要的作用。常规的安防方式是采用人员巡查&#xff0c;人力投入成本大而且效率低。周界一旦被破坏或入侵&#xff0c;会影响园区人员和资产安全&#xff0c;…...

    2024/5/4 23:54:44
  9. VB.net WebBrowser网页元素抓取分析方法

    在用WebBrowser编程实现网页操作自动化时&#xff0c;常要分析网页Html&#xff0c;例如网页在加载数据时&#xff0c;常会显示“系统处理中&#xff0c;请稍候..”&#xff0c;我们需要在数据加载完成后才能继续下一步操作&#xff0c;如何抓取这个信息的网页html元素变化&…...

    2024/5/4 12:10:13
  10. 【Objective-C】Objective-C汇总

    方法定义 参考&#xff1a;https://www.yiibai.com/objective_c/objective_c_functions.html Objective-C编程语言中方法定义的一般形式如下 - (return_type) method_name:( argumentType1 )argumentName1 joiningArgument2:( argumentType2 )argumentName2 ... joiningArgu…...

    2024/5/4 23:54:49
  11. 【洛谷算法题】P5713-洛谷团队系统【入门2分支结构】

    &#x1f468;‍&#x1f4bb;博客主页&#xff1a;花无缺 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! 本文由 花无缺 原创 收录于专栏 【洛谷算法题】 文章目录 【洛谷算法题】P5713-洛谷团队系统【入门2分支结构】&#x1f30f;题目描述&#x1f30f;输入格…...

    2024/5/4 23:54:44
  12. 【ES6.0】- 扩展运算符(...)

    【ES6.0】- 扩展运算符... 文章目录 【ES6.0】- 扩展运算符...一、概述二、拷贝数组对象三、合并操作四、参数传递五、数组去重六、字符串转字符数组七、NodeList转数组八、解构变量九、打印日志十、总结 一、概述 **扩展运算符(...)**允许一个表达式在期望多个参数&#xff0…...

    2024/5/4 14:46:12
  13. 摩根看好的前智能硬件头部品牌双11交易数据极度异常!——是模式创新还是饮鸩止渴?

    文 | 螳螂观察 作者 | 李燃 双11狂欢已落下帷幕&#xff0c;各大品牌纷纷晒出优异的成绩单&#xff0c;摩根士丹利投资的智能硬件头部品牌凯迪仕也不例外。然而有爆料称&#xff0c;在自媒体平台发布霸榜各大榜单喜讯的凯迪仕智能锁&#xff0c;多个平台数据都表现出极度异常…...

    2024/5/4 14:46:11
  14. Go语言常用命令详解(二)

    文章目录 前言常用命令go bug示例参数说明 go doc示例参数说明 go env示例 go fix示例 go fmt示例 go generate示例 总结写在最后 前言 接着上一篇继续介绍Go语言的常用命令 常用命令 以下是一些常用的Go命令&#xff0c;这些命令可以帮助您在Go开发中进行编译、测试、运行和…...

    2024/5/4 14:46:11
  15. 用欧拉路径判断图同构推出reverse合法性:1116T4

    http://cplusoj.com/d/senior/p/SS231116D 假设我们要把 a a a 变成 b b b&#xff0c;我们在 a i a_i ai​ 和 a i 1 a_{i1} ai1​ 之间连边&#xff0c; b b b 同理&#xff0c;则 a a a 能变成 b b b 的充要条件是两图 A , B A,B A,B 同构。 必要性显然&#xff0…...

    2024/5/5 2:25:33
  16. 【NGINX--1】基础知识

    1、在 Debian/Ubuntu 上安装 NGINX 在 Debian 或 Ubuntu 机器上安装 NGINX 开源版。 更新已配置源的软件包信息&#xff0c;并安装一些有助于配置官方 NGINX 软件包仓库的软件包&#xff1a; apt-get update apt install -y curl gnupg2 ca-certificates lsb-release debian-…...

    2024/5/4 21:24:42
  17. Hive默认分割符、存储格式与数据压缩

    目录 1、Hive默认分割符2、Hive存储格式3、Hive数据压缩 1、Hive默认分割符 Hive创建表时指定的行受限&#xff08;ROW FORMAT&#xff09;配置标准HQL为&#xff1a; ... ROW FORMAT DELIMITED FIELDS TERMINATED BY \u0001 COLLECTION ITEMS TERMINATED BY , MAP KEYS TERMI…...

    2024/5/4 12:39:12
  18. 【论文阅读】MAG:一种用于航天器遥测数据中有效异常检测的新方法

    文章目录 摘要1 引言2 问题描述3 拟议框架4 所提出方法的细节A.数据预处理B.变量相关分析C.MAG模型D.异常分数 5 实验A.数据集和性能指标B.实验设置与平台C.结果和比较 6 结论 摘要 异常检测是保证航天器稳定性的关键。在航天器运行过程中&#xff0c;传感器和控制器产生大量周…...

    2024/5/4 13:16:06
  19. --max-old-space-size=8192报错

    vue项目运行时&#xff0c;如果经常运行慢&#xff0c;崩溃停止服务&#xff0c;报如下错误 FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - JavaScript heap out of memory 因为在 Node 中&#xff0c;通过JavaScript使用内存时只能使用部分内存&#xff08;64位系统&…...

    2024/5/4 16:48:41
  20. 基于深度学习的恶意软件检测

    恶意软件是指恶意软件犯罪者用来感染个人计算机或整个组织的网络的软件。 它利用目标系统漏洞&#xff0c;例如可以被劫持的合法软件&#xff08;例如浏览器或 Web 应用程序插件&#xff09;中的错误。 恶意软件渗透可能会造成灾难性的后果&#xff0c;包括数据被盗、勒索或网…...

    2024/5/4 14:46:05
  21. JS原型对象prototype

    让我简单的为大家介绍一下原型对象prototype吧&#xff01; 使用原型实现方法共享 1.构造函数通过原型分配的函数是所有对象所 共享的。 2.JavaScript 规定&#xff0c;每一个构造函数都有一个 prototype 属性&#xff0c;指向另一个对象&#xff0c;所以我们也称为原型对象…...

    2024/5/5 3:37:58
  22. C++中只能有一个实例的单例类

    C中只能有一个实例的单例类 前面讨论的 President 类很不错&#xff0c;但存在一个缺陷&#xff1a;无法禁止通过实例化多个对象来创建多名总统&#xff1a; President One, Two, Three; 由于复制构造函数是私有的&#xff0c;其中每个对象都是不可复制的&#xff0c;但您的目…...

    2024/5/4 23:54:30
  23. python django 小程序图书借阅源码

    开发工具&#xff1a; PyCharm&#xff0c;mysql5.7&#xff0c;微信开发者工具 技术说明&#xff1a; python django html 小程序 功能介绍&#xff1a; 用户端&#xff1a; 登录注册&#xff08;含授权登录&#xff09; 首页显示搜索图书&#xff0c;轮播图&#xff0…...

    2024/5/4 9:07:39
  24. 电子学会C/C++编程等级考试2022年03月(一级)真题解析

    C/C++等级考试(1~8级)全部真题・点这里 第1题:双精度浮点数的输入输出 输入一个双精度浮点数,保留8位小数,输出这个浮点数。 时间限制:1000 内存限制:65536输入 只有一行,一个双精度浮点数。输出 一行,保留8位小数的浮点数。样例输入 3.1415926535798932样例输出 3.1…...

    2024/5/4 14:46:02
  25. 配置失败还原请勿关闭计算机,电脑开机屏幕上面显示,配置失败还原更改 请勿关闭计算机 开不了机 这个问题怎么办...

    解析如下&#xff1a;1、长按电脑电源键直至关机&#xff0c;然后再按一次电源健重启电脑&#xff0c;按F8健进入安全模式2、安全模式下进入Windows系统桌面后&#xff0c;按住“winR”打开运行窗口&#xff0c;输入“services.msc”打开服务设置3、在服务界面&#xff0c;选中…...

    2022/11/19 21:17:18
  26. 错误使用 reshape要执行 RESHAPE,请勿更改元素数目。

    %读入6幅图像&#xff08;每一幅图像的大小是564*564&#xff09; f1 imread(WashingtonDC_Band1_564.tif); subplot(3,2,1),imshow(f1); f2 imread(WashingtonDC_Band2_564.tif); subplot(3,2,2),imshow(f2); f3 imread(WashingtonDC_Band3_564.tif); subplot(3,2,3),imsho…...

    2022/11/19 21:17:16
  27. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机...

    win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”问题的解决方法在win7系统关机时如果有升级系统的或者其他需要会直接进入一个 等待界面&#xff0c;在等待界面中我们需要等待操作结束才能关机&#xff0c;虽然这比较麻烦&#xff0c;但是对系统进行配置和升级…...

    2022/11/19 21:17:15
  28. 台式电脑显示配置100%请勿关闭计算机,“准备配置windows 请勿关闭计算机”的解决方法...

    有不少用户在重装Win7系统或更新系统后会遇到“准备配置windows&#xff0c;请勿关闭计算机”的提示&#xff0c;要过很久才能进入系统&#xff0c;有的用户甚至几个小时也无法进入&#xff0c;下面就教大家这个问题的解决方法。第一种方法&#xff1a;我们首先在左下角的“开始…...

    2022/11/19 21:17:14
  29. win7 正在配置 请勿关闭计算机,怎么办Win7开机显示正在配置Windows Update请勿关机...

    置信有很多用户都跟小编一样遇到过这样的问题&#xff0c;电脑时发现开机屏幕显现“正在配置Windows Update&#xff0c;请勿关机”(如下图所示)&#xff0c;而且还需求等大约5分钟才干进入系统。这是怎样回事呢&#xff1f;一切都是正常操作的&#xff0c;为什么开时机呈现“正…...

    2022/11/19 21:17:13
  30. 准备配置windows 请勿关闭计算机 蓝屏,Win7开机总是出现提示“配置Windows请勿关机”...

    Win7系统开机启动时总是出现“配置Windows请勿关机”的提示&#xff0c;没过几秒后电脑自动重启&#xff0c;每次开机都这样无法进入系统&#xff0c;此时碰到这种现象的用户就可以使用以下5种方法解决问题。方法一&#xff1a;开机按下F8&#xff0c;在出现的Windows高级启动选…...

    2022/11/19 21:17:12
  31. 准备windows请勿关闭计算机要多久,windows10系统提示正在准备windows请勿关闭计算机怎么办...

    有不少windows10系统用户反映说碰到这样一个情况&#xff0c;就是电脑提示正在准备windows请勿关闭计算机&#xff0c;碰到这样的问题该怎么解决呢&#xff0c;现在小编就给大家分享一下windows10系统提示正在准备windows请勿关闭计算机的具体第一种方法&#xff1a;1、2、依次…...

    2022/11/19 21:17:11
  32. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”的解决方法...

    今天和大家分享一下win7系统重装了Win7旗舰版系统后&#xff0c;每次关机的时候桌面上都会显示一个“配置Windows Update的界面&#xff0c;提示请勿关闭计算机”&#xff0c;每次停留好几分钟才能正常关机&#xff0c;导致什么情况引起的呢&#xff1f;出现配置Windows Update…...

    2022/11/19 21:17:10
  33. 电脑桌面一直是清理请关闭计算机,windows7一直卡在清理 请勿关闭计算机-win7清理请勿关机,win7配置更新35%不动...

    只能是等着&#xff0c;别无他法。说是卡着如果你看硬盘灯应该在读写。如果从 Win 10 无法正常回滚&#xff0c;只能是考虑备份数据后重装系统了。解决来方案一&#xff1a;管理员运行cmd&#xff1a;net stop WuAuServcd %windir%ren SoftwareDistribution SDoldnet start WuA…...

    2022/11/19 21:17:09
  34. 计算机配置更新不起,电脑提示“配置Windows Update请勿关闭计算机”怎么办?

    原标题&#xff1a;电脑提示“配置Windows Update请勿关闭计算机”怎么办&#xff1f;win7系统中在开机与关闭的时候总是显示“配置windows update请勿关闭计算机”相信有不少朋友都曾遇到过一次两次还能忍但经常遇到就叫人感到心烦了遇到这种问题怎么办呢&#xff1f;一般的方…...

    2022/11/19 21:17:08
  35. 计算机正在配置无法关机,关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机...

    关机提示 windows7 正在配置windows 请勿关闭计算机 &#xff0c;然后等了一晚上也没有关掉。现在电脑无法正常关机以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;关机提示 windows7 正在配…...

    2022/11/19 21:17:05
  36. 钉钉提示请勿通过开发者调试模式_钉钉请勿通过开发者调试模式是真的吗好不好用...

    钉钉请勿通过开发者调试模式是真的吗好不好用 更新时间:2020-04-20 22:24:19 浏览次数:729次 区域: 南阳 > 卧龙 列举网提醒您:为保障您的权益,请不要提前支付任何费用! 虚拟位置外设器!!轨迹模拟&虚拟位置外设神器 专业用于:钉钉,外勤365,红圈通,企业微信和…...

    2022/11/19 21:17:05
  37. 配置失败还原请勿关闭计算机怎么办,win7系统出现“配置windows update失败 还原更改 请勿关闭计算机”,长时间没反应,无法进入系统的解决方案...

    前几天班里有位学生电脑(windows 7系统)出问题了&#xff0c;具体表现是开机时一直停留在“配置windows update失败 还原更改 请勿关闭计算机”这个界面&#xff0c;长时间没反应&#xff0c;无法进入系统。这个问题原来帮其他同学也解决过&#xff0c;网上搜了不少资料&#x…...

    2022/11/19 21:17:04
  38. 一个电脑无法关闭计算机你应该怎么办,电脑显示“清理请勿关闭计算机”怎么办?...

    本文为你提供了3个有效解决电脑显示“清理请勿关闭计算机”问题的方法&#xff0c;并在最后教给你1种保护系统安全的好方法&#xff0c;一起来看看&#xff01;电脑出现“清理请勿关闭计算机”在Windows 7(SP1)和Windows Server 2008 R2 SP1中&#xff0c;添加了1个新功能在“磁…...

    2022/11/19 21:17:03
  39. 请勿关闭计算机还原更改要多久,电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机怎么办...

    许多用户在长期不使用电脑的时候&#xff0c;开启电脑发现电脑显示&#xff1a;配置windows更新失败&#xff0c;正在还原更改&#xff0c;请勿关闭计算机。。.这要怎么办呢&#xff1f;下面小编就带着大家一起看看吧&#xff01;如果能够正常进入系统&#xff0c;建议您暂时移…...

    2022/11/19 21:17:02
  40. 还原更改请勿关闭计算机 要多久,配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以...

    配置windows update失败 还原更改 请勿关闭计算机&#xff0c;电脑开机后一直显示以以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;配置windows update失败 还原更改 请勿关闭计算机&#x…...

    2022/11/19 21:17:01
  41. 电脑配置中请勿关闭计算机怎么办,准备配置windows请勿关闭计算机一直显示怎么办【图解】...

    不知道大家有没有遇到过这样的一个问题&#xff0c;就是我们的win7系统在关机的时候&#xff0c;总是喜欢显示“准备配置windows&#xff0c;请勿关机”这样的一个页面&#xff0c;没有什么大碍&#xff0c;但是如果一直等着的话就要两个小时甚至更久都关不了机&#xff0c;非常…...

    2022/11/19 21:17:00
  42. 正在准备配置请勿关闭计算机,正在准备配置windows请勿关闭计算机时间长了解决教程...

    当电脑出现正在准备配置windows请勿关闭计算机时&#xff0c;一般是您正对windows进行升级&#xff0c;但是这个要是长时间没有反应&#xff0c;我们不能再傻等下去了。可能是电脑出了别的问题了&#xff0c;来看看教程的说法。正在准备配置windows请勿关闭计算机时间长了方法一…...

    2022/11/19 21:16:59
  43. 配置失败还原请勿关闭计算机,配置Windows Update失败,还原更改请勿关闭计算机...

    我们使用电脑的过程中有时会遇到这种情况&#xff0c;当我们打开电脑之后&#xff0c;发现一直停留在一个界面&#xff1a;“配置Windows Update失败&#xff0c;还原更改请勿关闭计算机”&#xff0c;等了许久还是无法进入系统。如果我们遇到此类问题应该如何解决呢&#xff0…...

    2022/11/19 21:16:58
  44. 如何在iPhone上关闭“请勿打扰”

    Apple’s “Do Not Disturb While Driving” is a potentially lifesaving iPhone feature, but it doesn’t always turn on automatically at the appropriate time. For example, you might be a passenger in a moving car, but your iPhone may think you’re the one dri…...

    2022/11/19 21:16:57