本文主要说明JAVA中的一些特性。
可变参数JAVA在进行方法调用的时候,必须要按照方法定义的变量进行参数传递,而若实际要传递的参数个数是不确定的,便可以将实际要传送的参数封装为数组的形式,但是这种形式看起来比较笨拙。
因此从JDK 1.5开始,为了解决任意多个参数的问题,JAVA提供了可变参数的概念,语法为:
[public|protected|private][static][final][abstract] returntype func(vartype ... var) {
[return returnvalue];
}
从参数的形式来看,用户可以使用vartype ... var的形式传递多个参数,而实际这多个参数传递到方法内部将以指定类型的数组进行保存:
public class Demo {
public static void main(String[] args) throws Exception {
System.out.println(func(new String[]{"Hello","world","java"}));
System.out.println(func("Hello","world","java"));
System.out.println(func());
}
public static String func(String ... var) {
String tmp = null;
for(String iter:var) {
tmp += iter;
}
return tmp;
}
}
执行结果为:
nullHelloworldjava nullHelloworldjava null
上面的结果可以接收任意数目的参数,也就实现了可变参数,从形式上看,也要直观些。
foreach这里的foreach也是一种循环的形式,在上一段代码中就已经示范过。
和一般的for循环写法不同,foreach的写法为:
for(vartype var:array) {
//statement;
}
public class Demo {
public static void main(String[] args) throws Exception {
String arr[] = new String[]{"Hello","world","java"};
for(String iter:arr) {
System.out.println(iter);
}
}
}
执行结果为:
Hello world java
从这里来看,其执行结果和一般的for循环是一致的。
静态导入在某些类中提供了很多静态方法,这意味着可以直接使用类名进行方法的调用,而如果该类处于其它包中,首先需要先将该包中的相关类导入。
比如下面的包:
package com.wood.myclass;
public class MyClass {
public static void func(){
String arr[] = new String[]{"Hello","world","java"};
for(String iter:arr) {
System.out.println(iter);
}
}
}
而可以实现如下调用:
import com.wood.myclass.MyClass;
public class Demo {
public static void main(String[] args) throws Exception {
MyClass.func();
}
}
而如果不希望出现类名的话,就可以使用静态导入,静态导入的形式为:
import static package.class.*;
而可以将上面的代码修改为:
import static com.wood.myclass.MyClass.*;
public class Demo {
public static void main(String[] args) throws Exception {
func();
}
}
其打印结果是一样的。
静态导入从形式上看,是将类MyClass中的所有静态方法全部导入了,因此便可以省略类型直接使用。而借用这个思想,若只是想调用某个静态方法,则可以使用下面的形式:
import static com.wood.myclass.MyClass.func;
public class Demo {
public static void main(String[] args) throws Exception {
func();
}
}
这里明确只导入MyClass中的func方法,其打印结果是一样的。
泛型C/C++中存在模板的定义,以便实现不同类型的参数适配和功能扩展。而JAVA中也存在泛型的概念,其实不管是C/C++中的模板还是JAVA中的泛型,其实都是泛型编程的体现。
该思想的核心在于:类属性或方法的参数在定义数据类型时,可以直接使用标记进行展位,在具体使用时才设置其对应的数据类型。
class A{ private T tmp; A(T in) { tmp = in; } void setTmp(T in) { this.tmp = in; } T getTmp() { return tmp; } void print() { System.out.println(tmp); } } public class Demo { public static void main(String[] args) throws Exception { A a = new A ("Hello"); System.out.println(a.getTmp()); a.setTmp("world"); a.print(); A aa = new A (10); System.out.println(aa.getTmp()); aa.setTmp(20); aa.print(); A aaa = new A("Hello"); System.out.println(aaa.getTmp().getClass()); System.out.println(aaa.getTmp()); aaa.setTmp("world"); aaa.print(); A aaaa = new A(10); System.out.println(aaaa.getTmp().getClass()); System.out.println(aaaa.getTmp()); aaaa.setTmp(20); aaaa.print(); func("Hello"); func(10); } public static void func(T tmp) { System.out.println(tmp); } }
执行结果为:
Hello world 10 20 class java.lang.String Hello world class java.lang.Integer 10 20 Hello 10
上面的代码构建了泛型类A和泛型方法func,从而可以接收不同的参数,从而实现了不同类型间的参数适配,但实际上,开发中单个的泛型中可能存在多个泛型类型,不过原理都是一样的。
同时如果设置了泛型,而实际传递的泛型类型为int、double等类型时,又是不可以的,需要使用对应的包装类,如上面的Integer。
而如果不设置泛型类型,在参数传递时,JAVA会自动分辨出对应的数据类型,并使用对应数据类型去接纳这些参数。不过实际开发中,最好还是不要这么使用,否则会带来较大的歧义。
通配符如果将上面的泛型方法设置为普通方法,并希望接收一个泛型参数的话,由于泛型并没有设置具体的泛型类型,而不同的泛型类型之间不能构成重载,因此此时就可以使用通配符来进行统一适配:
class A{ private T tmp; A(T in) { tmp = in; } void setTmp(T in) { this.tmp = in; } T getTmp() { return tmp; } void print() { System.out.println(tmp); } } public class Demo { public static void main(String[] args) throws Exception { A a = new A ("Hello"); A aa = new A (10); func(a); func(aa); } public static void func(A> tmp) { tmp.print(); } }
执行结果为:
Hello 10
这里将func方法中的泛型参数的泛型类型设置为通配符?,以适配不同的泛型类型,完成统一动作。
但是很显然上面的func方法中,只是获取了类属性进行打印,并没有进行修改操作,这样是OK的。但如果要在func中使用setter方法进行属性修改,就会出现问题,因为对于泛型来说,不同的泛型类型不能够互相转换。
而如果func方法只接收该类,并没有使用通配符的话,便会出现下面的结果:
class A{ private T tmp; A(T in) { tmp = in; } void setTmp(T in) { this.tmp = in; } T getTmp() { return tmp; } void print() { System.out.println(tmp); } } public class Demo { public static void main(String[] args) throws Exception { A a = new A ("Hello"); A aa = new A (10); func(a); func(aa); } public static void func(A tmp) { System.out.println(tmp.getTmp().getClass()); tmp.print(); tmp.setTmp(10.12); System.out.println(tmp.getTmp().getClass()); tmp.print(); } }
执行结果为:
class java.lang.String Hello class java.lang.Double 10.12 class java.lang.Integer 10 class java.lang.Double 10.12
上面的代码中,执行结果很奇怪。在未使用通配符的情况下,可以修改类属性,并实现不同类型的转换。而这种使用方法很显然是不科学的,因为会引起很大的歧义。因此实际开发中并不建议这样使用,要使用通配符加以限制。
除了通配符?外,还存在两个子通配符:
? extends class: 设置泛型上限,可以在声明和方法参数上使用 ? super class: 设置泛型下限,方法参数上使用
泛型上限意味着可以这设置对应class及其子类,泛型下限意味着只能设置class及其父类。
class A{ private T tmp; A(T in) { tmp = in; } void setTmp(T in) { this.tmp = in; } T getTmp() { return tmp; } void print() { System.out.println(tmp); } } public class Demo { public static void main(String[] args) throws Exception { A a = new A ("Hello"); A aa = new A (10); func(aa); foo(a); } public static void func(A extends Number> tmp) { tmp.print(); } public static void foo(A super String> tmp) { tmp.print(); } }
若设置了泛型的界限,那么上面的代码便只能分开调用了,因为不符合泛型界限的泛型类型会被禁止使用。
泛型接口除了类,还可以定义泛型接口。
interface IDemo{ public void print(T tmp); } class DemoImpl implements IDemo
{ public void print(P tmp) { System.out.println(tmp); } } public class Demo { public static void main(String[] args) throws Exception { IDemo
d = new DemoImpl (); d.print("Hello world."); } }
执行结果为:
Hello world.
上面的代码中在接口中设置了泛型类型,而在子类中也设置了泛型类型,在实例化子类对象所设置的泛型类型,也是接口中的泛型类型。
但同时子类也可以不设置泛型,而直接为父接口明确定义一个泛型类型。
interface IDemo{ public void print(T tmp); } class DemoImpl implements IDemo { public void print(String tmp) { System.out.println(tmp); } } public class Demo { public static void main(String[] args) throws Exception { IDemo d = new DemoImpl(); d.print("Hello world."); } }
执行结果是一样的,不过便不能使用其它泛型类型作为参数类型了。
泛型方法前面的代码中还提到了泛型方法,这里再看一下:
public class Demo {
public static void main(String[] args) throws Exception {
func("Hello");
func(10);
}
public static void func(T tmp) {
System.out.println(tmp);
}
}
这里泛型方法并不在泛型类中定义,但仍可以使用泛型。在方法的返回值前定义了泛型标记,这样就可以在方法的返回值或参数类型上使用泛型标记进行声明。
枚举 枚举定义enum Color {
RED,
GREEN,
BLUE;
}
public class Demo {
public static void main(String[] args) throws Exception {
Color c = Color.RED;
System.out.println(c);
}
}
执行结果为:
RED
上面就是枚举的简单使用,但其实枚举只是类结构的加强,JAVA中使用enum定义的枚举类就相当于默认继承java.lang.Enum类,定义为:
public abstract class Enum> extends Object implements Constable, Comparable , Serializable
其中的常用方法为:
protected Enum(String name, int ordinal) // Sole constructor. final int ordinal() // Returns the ordinal of this enumeration constant (its position in its enum declaration, where the initial constant is assigned an ordinal of zero). final String name() // Returns the name of this enum constant, exactly as declared in its enum declaration.
这里看一下这两个方法:
enum Color {
RED,
GREEN,
BLUE;
}
public class Demo {
public static void main(String[] args) throws Exception {
Color c = Color.RED;
System.out.println(c.ordinal());
System.out.println(c.name());
for(Color tmp:c.values()) {
System.out.println(tmp.ordinal() + " " + tmp.name());
}
}
}
执行结果为:
0 RED 0 RED 1 GREEN 2 BLUE
上面的ordinal方法会获取枚举对象的序号,name方法会获取枚举对象的名字,values可以通过类调用或方法调用,以对象数组形式返回枚举类中的所有对象。
定义其它结构既然enum是类结构的加强,那么便也可以使用enum来重新定义枚举。
enum Color {
RED("red"),GREEN("green"),BLUE("blue");
private String name;
private Color(String name) {
this.name = name;
}
public String toString() {
return name;
}
}
public class Demo {
public static void main(String[] args) throws Exception {
Color c = Color.RED;
System.out.println(c.ordinal());
System.out.println(c.name());
for(Color tmp:Color.values()) {
System.out.println(tmp);
}
}
}
执行结果为:
0 RED red green blue
上面使用enum定义了枚举结构,但是有两点需要注意:
- 构造方法不能使用public声明,如果没有无参构造,要手动调用构造传递参数
- 枚举对象必须要放在首行,随后才能够定义属性、构造、方法
既然如此,enum也可以实现接口等操作,但一般枚举结构实际开发中也不会用的那么复杂。
Annotation在某些代码的方法前可能会使用@来说明,这就是注解,利用注解技术可以回避面向对象中覆写方法名称固定的问题,并且可读性也很强。
@Override当进行方法覆写时,为了保证子类所覆写的方法是父类中定义过的方法,可以加上@Override注解,这样若用户覆写时出现了错误,就可以在编译时检查出来。
class A {
@Override
public String toString() {
return "Hello world.";
}
}
public class Demo {
public static void main(String[] args) throws Exception {
A a = new A();
System.out.println(a);
}
}
虽然@Override在上面的代码中写与不写区别不大,但是不写在正确覆写时没有问题,但是覆写错误无法验证。
@Deprecated该注解表示该方法不建议使用,如果开发中继续使用该方法,就会出现警告信息。
@SuppressWarnings该注解可以在有可能出现警告信息的代码上使用,以压制所有出现的警告信息。
class A{ private T tmp; A(T in) { tmp = in; } void setTmp(T in) { this.tmp = in; } T getTmp() { return tmp; } void print() { System.out.println(tmp); } } public class Demo { @SuppressWarnings({"rawtypes","unchecked"}) public static void main(String[] args) throws Exception { A a = new A ("Hello"); System.out.println(a.getTmp()); a.setTmp("world"); a.print(); } }
上面的代码中在编译时由于setTmp方法没有对变量做检查,因此会出现警告,而使用该注解可以去除这些警告信息。
@FunctionalInterface表示该接口为函数式接口,里面只允许定义一个抽象方法。可与Lambda表达式一起使用。
LambdaC/C++中后来也扩展了Lambda表达式,JAVA中也存在这一特性。
Lambda表达式指的是应用在单一抽象方法(Single Abstract Method, SAM)接口环境下的一种简化定义形式,可以用于解决匿名内部类的定义复杂问题。
先看下匿名内部类的使用:
interface IDemo {
public void print();
}
public class Demo {
public static void main(String[] args) throws Exception {
func(new IDemo() {
@Override
public void print() {
System.out.println("Hello world");
}
});
}
public static void func(IDemo tmp) {
tmp.print();
}
}
如果使用Lambda表达式:
interface IDemo {
public void print();
}
public class Demo {
public static void main(String[] args) throws Exception {
func(()->System.out.println("Hello world"));
}
public static void func(IDemo tmp) {
tmp.print();
}
}
两者的执行结果一致,但是Lambda的形式就要比匿名内部类简洁很多。Lambda表达式形式为:
(var)->statements
其中var表示参数,statements表示函数体。
interface IDemo {
public void print();
}
interface IDemo2 {
public void print(String str);
}
public class Demo {
public static void main(String[] args) throws Exception {
func(()->System.out.println("Hello world"));
foo((str)-> {
System.out.println(str);
});
}
public static void func(IDemo tmp) {
tmp.print();
}
public static void foo(IDemo2 tmp) {
tmp.print("Hello world");
}
}
执行结果为:
Hello world Hello world
接口IDemo中print方法为无参方法,而IDemo2中的print方法存在一个String参数,因此在实际上main中foo的参数为函数体,而参数传入在foo的定义中,这样看起来和普通的方法不同,普通方法调用是方法中为函数体,调用处传参,这里倒成了方法中传参,调用处为函数体。
利用上面理解,看下面的代码:
@FunctionalInterface
interface IDemo {
public int add(int ... args);
static int sum(int ... args) {
int res = 0;
for(int tmp:args) {
res += tmp;
}
return res;
}
}
public class Demo {
public static void main(String[] args) throws Exception {
func((int ... parm)->IDemo.sum(parm));
}
public static void func(IDemo tmp) {
System.out.println(tmp.add(1,2,3));
}
}
上面的代码首先定义了sum的静态方法,然后使用Lambda表达式传递了可变参数,调用sum进行计算。
方法引用JAVA也支持引用操作,JAVA定义了4种操作形式:
- 引用静态方法:类名称::static方法名
- 引用某个对象的方法:实例化对象::普通方法
- 引用特定类型的方法:特定类::普通方法
- 引用构造方法:类名::new
@FunctionalInterface interface IDemo{ public T trans(P tmp); } public class Demo { public static void main(String[] args) throws Exception { IDemo func = String::valueOf; System.out.println(func.trans(1000)); } }
方法引用其实就是构造一个函数式接口去接收要应用的方法,然后实现调用。这里使用IDemo的trans接收String的valueOf方法,实现调用。
普通方法引用@FunctionalInterface interface IDemo{ public T trans(); } public class Demo { public static void main(String[] args) throws Exception { String str = "Hello world"; IDemo func = str::toUpperCase; System.out.println(func.trans()); } }
和上边的逻辑式一样的。
引用特定类的方法@FunctionalInterface interface IDemo{ public int trans(T var1,T var2); } public class Demo { public static void main(String[] args) throws Exception { IDemo func = String::compareTo; System.out.println(func.trans("Hello","world")); } }
这里“Hello”.compareTo("world")在引用时就变成了func.trans("Hello","world"),逻辑仍然是相同的。
引用构造方法@FunctionalInterface interface IDemo{ public T trans(char[] str); } public class Demo { public static void main(String[] args) throws Exception { IDemo func = String::new; System.out.println(func.trans(new char[]{'h','e','l','l','o'})); } }
这里构造的是String(char[] str)形式,逻辑仍然是相同的。
内建函数式接口对于函数式接口来说,最多有四类:
- 有参数有返回值
- 有参数无返回值
- 无参数有返回值
- 判断真假
在java.util.function种存在4个核心的函数式接口:
Function:功能型接口@FunctionalInterface public interface FunctionConsumer:消费型接口{ R apply(T t); }
@FunctionalInterface public interface ConsumerSupplier:供给型接口{ void accept(T t); }
@FunctionalInterface public interface SupplierPredicate:断言型接口{ T get(); }
@FunctionalInterface public interface Predicate{ boolean test(T t); }
代码示例为:
import java.util.function.*;
class A {
static private String name = "Hello world";
static String valueOf(int tmp) {
return "" + tmp;
}
static void print(String str) {
System.out.println(str);
}
static String getName() {
return name;
}
static boolean equal(String tmp) {
return name.equalsIgnoreCase(tmp);
}
}
public class Demo {
public static void main(String[] args) throws Exception {
Function func1 = A::valueOf;
System.out.println(func1.apply(1000));
Consumer func2 = A::print;
func2.accept("Hello world");
Supplier func3 = A::getName;
System.out.println(func3.get());
Predicate func4 = A::equal;
System.out.println(func4.test("abcd"));
}
}
执行结果为:
1000 Hello world Hello world false
这里的内建函数式接口和之前的方法引用是一样的逻辑,无非是JAVA提供的接口而已。



