JAVA基础编程——tips

本文主要说明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<T> {
    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<String> a = new A<String>("Hello");
		System.out.println(a.getTmp());
		a.setTmp("world");
		a.print();
		
		A<Integer> aa = new A<Integer>(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<T> 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<T> {
    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<String> a = new A<String>("Hello");
		
		A<Integer> aa = new A<Integer>(10);
		
		func(a);
		func(aa);
	}
	
	public static void func(A<?> tmp) {
		tmp.print();
	}
}

执行结果为:

Hello
10

这里将func方法中的泛型参数的泛型类型设置为通配符?,以适配不同的泛型类型,完成统一动作。

但是很显然上面的func方法中,只是获取了类属性进行打印,并没有进行修改操作,这样是OK的。但如果要在func中使用setter方法进行属性修改,就会出现问题,因为对于泛型来说,不同的泛型类型不能够互相转换。

而如果func方法只接收该类,并没有使用通配符的话,便会出现下面的结果:

class A<T> {
    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<String> a = new A<String>("Hello");
		
		A<Integer> aa = new A<Integer>(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<T> {
    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<String> a = new A<String>("Hello");
		
		A<Integer> aa = new A<Integer>(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<T> {
    public void print(T tmp);
}

class DemoImpl<P> implements IDemo<P> {
    public void print(P tmp) {
	    System.out.println(tmp);
	}
}

public class Demo {
	public static void main(String[] args) throws Exception {
 		IDemo<String> d = new DemoImpl<String>();
		d.print("Hello world.");
	}
}

执行结果为:

Hello world.

上面的代码中在接口中设置了泛型类型,而在子类中也设置了泛型类型,在实例化子类对象所设置的泛型类型,也是接口中的泛型类型。

但同时子类也可以不设置泛型,而直接为父接口明确定义一个泛型类型。

interface IDemo<T> {
    public void print(T tmp);
}

class DemoImpl implements IDemo<String> {
    public void print(String tmp) {
	    System.out.println(tmp);
	}
}

public class Demo {
	public static void main(String[] args) throws Exception {
 		IDemo<String> d = new DemoImpl();
		d.print("Hello world.");
	}
}

执行结果是一样的,不过便不能使用其它泛型类型作为参数类型了。

泛型方法

前面的代码中还提到了泛型方法,这里再看一下:

public class Demo {
	public static void main(String[] args) throws Exception {
		func("Hello");
		func(10);
	}
	
	public static<T> 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<E extends Enum<E>>
extends Object
implements Constable, Comparable<E>, 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<T> {
    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<String> a = new A<String>("Hello");
		System.out.println(a.getTmp());
		a.setTmp("world");
		a.print();
	}
}

上面的代码中在编译时由于setTmp方法没有对变量做检查,因此会出现警告,而使用该注解可以去除这些警告信息。

@FunctionalInterface

表示该接口为函数式接口,里面只允许定义一个抽象方法。可与Lambda表达式一起使用。

Lambda

C/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<T,P> {
    public T trans(P tmp);
}

public class Demo {
	public static void main(String[] args) throws Exception {
 		IDemo<String, Integer> func = String::valueOf;
		
		System.out.println(func.trans(1000));
	}
}

方法引用其实就是构造一个函数式接口去接收要应用的方法,然后实现调用。这里使用IDemo的trans接收String的valueOf方法,实现调用。

普通方法引用

@FunctionalInterface
interface IDemo<T> {
    public T trans();
}

public class Demo {
	public static void main(String[] args) throws Exception {
		String str = "Hello world";
 		IDemo<String> func = str::toUpperCase;
		
		System.out.println(func.trans());
	}
}

和上边的逻辑式一样的。

引用特定类的方法

@FunctionalInterface
interface IDemo<T> {
    public int trans(T var1,T var2);
}

public class Demo {
	public static void main(String[] args) throws Exception {
 		IDemo<String> func = String::compareTo;
		
		System.out.println(func.trans("Hello","world"));
	}
}

这里“Hello”.compareTo("world")在引用时就变成了func.trans("Hello","world"),逻辑仍然是相同的。

引用构造方法

@FunctionalInterface
interface IDemo<T> {
    public T trans(char[] str);
}

public class Demo {
	public static void main(String[] args) throws Exception {
 		IDemo<String> 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 Function<T, R> {

    /**
     * Applies this function to the given argument.
     *
     * @param t the function argument
     * @return the function result
     */
    R apply(T t);
}

Consumer:消费型接口

@FunctionalInterface
public interface Consumer<T> {

    /**
     * Performs this operation on the given argument.
     *
     * @param t the input argument
     */
    void accept(T t);
}

Supplier:供给型接口

@FunctionalInterface
public interface Supplier<T> {

    /**
     * Gets a result.
     *
     * @return a result
     */
    T get();
}

Predicate:断言型接口

@FunctionalInterface
public interface Predicate<T> {

    /**
     * Evaluates this predicate on the given argument.
     *
     * @param t the input argument
     * @return {@code true} if the input argument matches the predicate,
     * otherwise {@code false}
     */
    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<Integer,String> func1 = A::valueOf;
		System.out.println(func1.apply(1000));
		
		Consumer<String> func2 = A::print;
		func2.accept("Hello world");
		
		Supplier<String> func3 = A::getName;
		System.out.println(func3.get());
		
		Predicate<String> func4 = A::equal;
		System.out.println(func4.test("abcd"));
		
		
	}
}

执行结果为:

1000
Hello world
Hello world
false

这里的内建函数式接口和之前的方法引用是一样的逻辑,无非是JAVA提供的接口而已。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值